Howitworks...
Instep5,wedefinedtheswipingpropertyonthestate.Thispropertyisjusta
Booleanthatwillbesettotruewhenthedraggingstartsandtofalsewhenithas
completed.Weneedthisinformationinordertolocktheverticalscrollingonthe
listwhiledraggingaroundtheitem.
Instep7,wedefinedthecontentofeachrowinthelist.TheonDragStartproperty
receivesthehandleToggleSwipemethod,whichwillbeexecutedwhenthedragging
starts.Wearealsogoingtoexecutethesamemethodwhenthedraggingis
completed.
Inthesamestep,wealsosendthehandleRemoveContactmethodtoeachitem.Asthe
namesuggests,wearegoingtoremovethecurrentitemfromthelistwhenthe
userswipesitout.
Instep11,wedefineddefaultPropsandstatefortheitemcomponent.Inpast
recipes,wehavebeencreatinganimationsusingasinglevalue,butforthiscase
weneedtohandlethexandycoordinates,sowe'llneedaninstance
ofAnimated.ValueXY.Internally,thisclasshandlestwoAnimated.Valueinstances,and
thereforetheAPIisalmostidenticaltothosewe'veseenbefore.
Instep12,PanRespondergetscreated.ThegesturesysteminReactNative,likethe
eventsysteminthebrowser,handlesgesturesintwophaseswhenthere'satouch
event:thecaptureandthebubble.Inourcase,weneedtousethecapturephase
tofigureoutwhetherthecurrenteventispressingtheitemorwhetherit'strying
todragit.onMoveShouldSetPanResponderCapturewillcapturetheevent.Then,weneed
todecidewhetherwe'lldragtheitemornotbyreturningtrueorfalse.
TheonPanResponderMovepropwillgetthevaluesfromtheanimationoneachframe,
whichwillbeappliedtothepanobjectinthestate.Weneedto
useAnimated.eventtoaccesstheanimationvaluesforeachframe.Inthiscase,we
onlyneedthexvalue.Later,we'llusethisvaluetorunadifferentanimation
whilereturningtheelementtoitsoriginalplaceorremovingitfromthescreen.
TheonPanResponderReleasefunctionwillbeexecutedwhentheuserreleasesthe
item.If,foranyotherreason,thedragginggets
interrupted,onPanResponderTerminatewillgetexecutedinstead.
Instep13,weneedtocheckwhetherthecurrenteventisasimplepressora
dragging.Wecandothisbycheckingthedeltaonthex-axis.Ifthetouchevent
hasbeenmovedmorethantwopixels,thentheuseristryingtodragtheitem,
otherwise,they'retryingtopressthebutton.Weevaluatethedifferenceasan
absolutenumberbecausethemovementcouldbefromlefttorightorrightto
left,andwewanttoaccommodatebothmovements.
Instep14,weneedtogetthedistancetheitemhasmovedwithrespecttothe
widthofthedevice.Ifthisdistanceisbelowourthresholdwedefined
insetThreshold,thenweneedtoremovetheseitems.Wearedefining
theconfigobjectforeachanimation,whichwillotherwisereturntheitemtothe
originalposition.Butifweneedtoremovetheitem,wecheckthedirectionand
settheconfigurationaccordingly.
Instep16,wedefinedtheJSX.Wesetthestylesthatwewanttoanimate
onAnimated.View.Inthiscase,it'stheleftproperty,butinsteadofmanually
creatinganobject,wecancallthegetLayoutmethodfromourinstance
ofAnimated.ValueXYthatwestoredinstate.pan,whichreturnsthetopandleft
propertieswiththeirexistingvalues.
Inthesamestep,wealsosettheeventhandlersforAnimated.Viewbyspreading
outthis.panResponder.panHandlerswithaspreadoperator,whichbindsthedragging
configurationwedefinedinthepreviousstepstoAnimated.View.
WealsodefinedacalltotheonPresscallbackfromprops,passinginthe
currentcontactinformation.
Seealso
YoucanfindthePanResponderAPIdocumentationat:
https://facebook.github.io/react-native/docs/panresponder.html
CreatingaFacebookreactionswidget
Inthisrecipe,we'llbecreatingacomponentthatemulatestheFacebookreaction
widget.Wewillhavealikebuttonimagewhich,whenpressed,willshowfive
icons.Therowoficonswilluseastaggeredslide-inanimationwhileincreasing
opacityfrom0to1.
Gettingready
Let'screateanemptyappcalledfacebook-widget.
Wearegoingtoneedsomeimagestodisplayafaketimeline.Afewpicturesof
yourcatwillwork,oryoucanusethecatpicturesincludedinthecorresponding
repositoryonGitHub(https://github.com/warlyware/react-native-cookbook/tree/master/ch
apter-7/facebook-widget).We'llalsoneedfiveiconstodisplaythefivereactions,
suchas,angry,laughing,heart,andsurprised,whichcanalsobefoundinthe
correspondingrepository.
Tostartwe'llcreatetwoJavaScriptfilesinourempty
app:Reactions/index.jsandReactions/Icon.js.Weneedtocopyourcatpicturesto
animages/folderintherootoftheapp,andthereactioniconsshouldbeplaced
inReactions/images.
Howtodoit...
1. WearegoingtobecreatingafakeFacebooktimelineontheAppclass.Let's
startbyimportingthedependencies,asfollows:
importReactfrom'react';
import{
Dimensions,
Image,
Text,
ScrollView,
StyleSheet,
SafeAreaView,
}from'react-native';
importReactionsfrom'./Reactions';
2. We'llneedtoimportsomeimagestorenderinourtimeline.TheJSXinthis
stepisverysimple:it'sjustatoolbar,aScrollViewwithtwoImage,and
twoReactioncomponents,asfollows:
constimage1=require('./images/01.jpg');
constimage2=require('./images/02.jpg');
const{width}=Dimensions.get('window');
constApp=()=>(
<SafeAreaViewstyle={styles.main}>
<Textstyle={styles.toolbar}>Reactions</Text>
<ScrollViewstyle={styles.content}>
<Imagesource={image1}style={styles.image}resizeMode="cover"/>
<Reactions/>
<Imagesource={image2}style={styles.image}resizeMode="cover"/>
<Reactions/>
</ScrollView>
</SafeAreaView>
);
exportdefaultApp;
3. Weneedtoaddsomebasicstylesforthiscomponent,asfollows:
conststyles=StyleSheet.create({
main:{
flex:1,
},
toolbar:{
backgroundColor:'#3498db',
color:'#fff',
fontSize:22,
padding:20,
textAlign:'center',
},
content:{
flex:1,
},
image:{
width,
height:300,
},
});
4. WearereadytostartworkingontheReactionscomponentofthisrecipe.
Let'sstartbyimportingdependencies,asfollows.Wewillbuildoutthe
importedIconcomponentinlatersteps:
importReact,{Component}from'react';
import{
Image,
Text,
TouchableOpacity,
StyleSheet,
View,
}from'react-native';
importIconfrom'./Icon';
5. Let'sdefinedefaultPropsandtheinitialstatenext.We'llalsoneedtorequire
thelikeiconimagetodisplayitonscreen,asfollows:
constimage=require('./images/like.png');
exportdefaultclassReactionsextendsComponent{
staticdefaultProps={
icons:[
'like','heart','angry','laughing','surprised',
],
};
state={
show:false,
selected:'',
};
//Definedatlatersteps
}
6. Let'sdefinetwomethods:onethatsetstheselectedvalueofstatetothe
selectedreaction,andanotherthattogglestheshowvalueofstatetoshowor
hidetherowofreactionsaccordingly,asfollows:
onSelectReaction=(reaction)=>{
this.setState({
selected:reaction,
});
this.toggleReactions();
}
toggleReactions=()=>{
this.setState({
show:!this.state.show,
});
};
7. We'lldefinetherendermethodforthiscomponent.Wearegoingtodisplay
animage,whichwhenpressed,willcallthetoggleReactionsmethodthatwe
definedpreviously,asfollows:
render(){
const{style}=this.props;
const{selected}=this.state;
return(
<Viewstyle={[style,styles.container]}>
<TouchableOpacityonPress={this.toggleReactions}>
<Imagesource={image}style={styles.icon}/>
</TouchableOpacity>
<Text>{selected}</Text>
{this.renderReactions()}
</View>
);
}
8. You'llnoticeinthisstepthatwe'recallingtherenderReactionsmethod.Next,
we'llrenderalloftheiconsthatwewanttodisplaywhentheuserpresses
themainreactionbutton,asfollows:
renderReactions(){
const{icons}=this.props;
if(this.state.show){
return(
<Viewstyle={styles.reactions}>
{icons.map((name,index)=>(
<Icon
key={index}
name={name}
delay={index*100}
index={index}
onPress={this.onSelectReaction}
/>
))
}
</View>
);
}
}
9. Weneedtosestylesforthiscomponent.We'llsetsizesforthereactionicon
imagesanddefinesomepadding.Thereactionscontainerwillhaveaheight
of0,sincetheiconswillbefloating,andwedon'twantanyextraspace
added,asfollows:
conststyles=StyleSheet.create({
container:{
padding:10,
},
icon:{
width:30,
height:30,
},
reactions:{
flexDirection:'row',
height:0,
},
});
10. TheIconcomponentiscurrentlymissing,soifwetrytorunourappatthis
point,itwillfail.Let'sbuildoutthiscomponentbyopeningthe
Reactions/Icon.jsfileandaddingtheimportsforthecomponent,asfollows:
importReact,{Component}from'react';
import{
Animated,
Dimensions,
Easing,
Image,
StyleSheet,
TouchableOpacity,
View,
}from'react-native';
11. Let'sdefinetheiconswe'llbeusing.Wearegoingtouseanobjectforthe
iconssothatwecaneasilyretrieveeachimagebyitskeyname,asfollows:
consticons={
angry:require('./images/angry.png'),
heart:require('./images/heart.png'),
laughing:require('./images/laughing.png'),
like:require('./images/like.png'),
surprised:require('./images/surprised.png'),
};
12. NowweshoulddefinedefaultPropsforthiscomponent.Wedon'tneedto
defineaninitialstate:
exportdefaultclassIconextendsComponent{
staticdefaultProps={
delay:0,
onPress:()=>{},
};
}
13. Theiconsshouldappearonscreenviaananimation,sowe'llneedtocreate
andruntheanimationwhenthecomponentismounted,asfollows:
componentWillMount(){
this.animatedValue=newAnimated.Value(0);
}
componentDidMount(){
const{delay}=this.props;
Animated.timing(
this.animatedValue,
{
toValue:1,
duration:200,
easing:Easing.elastic(1),
delay,
}
).start();
}
14. Whentheiconispressed,weneedtoexecutetheonPresscallbacktoinform
theparentthatareactionwasselected.Wewillsendthenameofthe
reactionasaparameter,asfollows:
onPressIcon=()=>{
const{onPress,name}=this.props;
onPress(name);
}
15. Thelastpieceofthepuzzleistherendermethod,wherewe'lldefinetheJSX
forthiscomponent,asfollows:
render(){
const{name,index,onPress}=this.props;
constleft=index*50;
consttop=this.animatedValue.interpolate({
inputRange:[0,1],
outputRange:[10,-95],
});
constopacity=this.animatedValue;
return(
<Animated.View
style={[
styles.icon,
{top,left,opacity},
]}
>
<TouchableOpacityonPress={this.onPressIcon}>
<Imagesource={icons[name]}style={styles.image}/>
</TouchableOpacity>
</Animated.View>
);
}
16. Asthefinalstep,we'lladdstylesforeachicon.Weneedtheiconstofloat,
sowe'llsetpositiontoabsoluteandwidthandheightto40pixels.Afterthis
change,weshouldbeabletorunourapp:
icon:{
position:'absolute',
},
image:{
width:40,
height:40,
},
});
17. Thefinalappshouldlooksomethinglikethisscreenshot:
Howitworks...
Instep2,wedefinedtheReactionscomponentinthetimeline.Fornow,wearenot
focusingonhandlingdata,butratherondisplayingtheUI.Therefore,wearenot
sendinganycallbackviaReactionspropstogettheselectedvalue.
Instep5,wedefineddefaultPropsandtheinitialstate.
Wehavetwopropertiesinthestate:
TheshowpropisaBoolean.Weuseittotogglethereactionsiconswhenthe
userpressesthemainbutton.Whenfalse,wehidethereactions,and
whentrue,weruntheanimationtoshoweachicon.
selectedcontainsthecurrentselection.Everytimeanewreactiongets
selected,wearegoingtoupdatethisprop.
Instep8,werendertheicons.Here,weneedtosendthenameoftheiconto
everyinstancecreated.Wealsosendadelayof100millisecondsforeachicon,
whichwillcreateanicestaggeranimation.TheonPresspropreceives
theonSelectReactionmethoddefinedinstep6,whichsetstheselectedreaction
onstate.
Instep13,wecreatetheanimation.First,wedefinetheanimatedValuevariable
usingtheAnimated.Valuehelper,which,asmentionedinpreviousrecipes,isthe
classresponsibleforholdingthevalueforeachframeintheanimation.Assoon
asthecomponentismounted,weruntheanimation.Theanimationsprogress
from0to1,withadurationof200millisecondsandusinganelasticeasing
function,andwedelaytheanimationbasedonthereceiveddelayprop.
Instep15,wedefinedtheJSXfortheIconcomponent.Hereweanimate
thetopandopacityproperties.Forthetopproperty,weneedtointerpolatethe
valuesfromanimatedValue,sothattheiconmoves95pixelsupfromitsoriginal
position.Therequiredvaluesfortheopacitypropertyarefrom0to1,andsince
wedon'tneedtointerpolateanythingtoaccomplishthis,wecan
useanimatedValuedirectly.
Theleftvalueiscalculatedbasedontheindex:wejustmovetheicon50pixelsto
theleftofthepreviousicon,whichwillavoidrenderingtheiconsallinthe
sampleplace.
Displayingimagesinfullscreen
Inthisrecipe,we'llcreateatimelineofimages.Whentheuserpressesanyofthe
images,itwillfullscreentheimagewithablackbackground.
Wewilluseanopacityanimationforthebackground,andwe'llslidetheimage
infromitsoriginalposition.
Gettingready
Let'screateanemptyappcalledphoto-viewer.
Inaddition,we'llalsocreatePostContainer/index.jsforshowingeachimageinthe
timeline,andPhotoViewer/index.jsforshowingtheselectedimageinfullscreen.
Youcaneitherusetheimagesincludedinthisrecipe'srepositoryhostedon
GitHub(https://github.com/warlyware/react-native-cookbook/tree/master/chapter-7/photo-v
iewer),oruseafewphotosofyourown.Placetheminanimagesfolderintheroot
oftheproject.
Howtodoit...
1. WearegoingtodisplayatimelinewithimagesintheAppclass.Let'simport
allofthedependencies,includingthetwoothercomponentswe'llbuildout
inlatersteps,asfollows:
importReact,{Component}from'react';
import{
Dimensions,
Image,
Text,
ScrollView,
StyleSheet,
SafeAreaView,
}from'react-native';
importPostContainerfrom'./PostContainer';
importPhotoViewerfrom'./PhotoViewer';
2. Inthisstep,we'lldefinethedatathatwearegoingtorender.It'sjusta
simplearrayofobjectscontainingtitleandimage,asfollows:
constimage1=require('./images/01.jpg');
constimage2=require('./images/02.jpg');
constimage3=require('./images/03.jpg');
constimage4=require('./images/04.jpg');
consttimeline=[
{title:'Enjoyingthefireworks',image:image1},
{title:'ClimbingtheMountFuji',image:image2},
{title:'Checkmylastpicture',image:image3},
{title:'Sakurasarebeautiful!',image:image4},
];
3. Nowweneedtodeclaretheinitialstateofthiscomponent.Wewillupdate
theselectedandpositionpropertieswhenanyoftheimagesgetspressed,as
follows:
exportdefaultclassAppextendsComponent{
state={
selected:null,
position:null,
};
//Definedinfollowingsteps
}
4. Inordertoupdatestate,wearegoingtodeclaretwomethods:onetosetthe
valueoftheimagethathasbeenpressedandanothertoremovethose
valueswhentheviewergetsclosed:
showImage=(selected,position)=>{
this.setState({
selected,
position,
});
}
closeViewer=()=>{
this.setState({
selected:null,
position:null,
});
}
5. Nowwearereadytoworkontherendermethod.Herewe'llneedtorender
eachimageinsideScrollViewsothelistwillbescrollable,asfollows:
render(){
return(
<SafeAreaViewstyle={styles.main}>
<Textstyle={styles.toolbar}>Timeline</Text>
<ScrollViewstyle={styles.content}>
{
timeline.map((post,index)=>
<PostContainerkey={index}post={post}
onPress={this.showImage}/>
)
}
</ScrollView>
{this.renderViewer()}
</SafeAreaView>
);
}
6. Inthepreviousstep,wearecallingtherenderViewermethod.Herewe'llshow
theviewercomponentonlyifthere'sapostselectedinthestate.Wearealso
sendingtheinitialpositiontostarttheanimationandacallbacktoclosethe
viewer,asfollows:
renderViewer(){
const{selected,position}=this.state;
if(selected){
return(
<PhotoViewer
post={selected}
position={position}
onClose={this.closeViewer}
/>
);
}
}
7. Thestylesforthiscomponentareverysimple,onlysomecolorsand
padding,asfollows:
conststyles=StyleSheet.create({
main:{
backgroundColor:'#ecf0f1',
flex:1,
},
toolbar:{
backgroundColor:'#2c3e50',
color:'#fff',
fontSize:22,
padding:20,
textAlign:'center',
},
content:{
flex:1,
},
});
8. Thetimelineiscomplete,butifwetrytorunourapp,itwillfail.Let'swork
onthePostContainercomponent.We'llstartbyimportingthedependencies,as
follows:
importReact,{Component}from'react';
import{
Dimensions,
Image,
Text,
TouchableOpacity,
StyleSheet,
View,
}from'react-native';
9. Weonlyneedtwopropsforthiscomponent.Thepostpropwillreceivethe
imagedata,titleandimage,andtheonPresspropisacallbackthatwe'll
executewhentheimagegetspressed,asfollows:
const{width}=Dimensions.get('window');
exportdefaultclassPostContainerextendsComponent{
staticdefaultProps={
onPress:()=>{},
};
//Definedonfollowingsteps
}
10. ThiscomponentwillbeinsideofScrollView.Thismeansitspositionwillbe
changingwhentheuserstartsscrollingthecontent.Whenpressingthe
image,weneedtogetthecurrentpositiononthescreenandsendthis
informationtotheparentcomponent,asfollows:
onPressImage=(event)=>{
const{onPress,post}=this.props;
this.refs.main.measure((fx,fy,width,height,pageX,pageY)=>{
onPress(post,{
width,
height,
pageX,
pageY,
});
});
}
11. It'stimetodefinetheJSXforthiscomponent.Tokeepthingssimple,we
areonlygoingtorenderimageandtitle:
render(){
const{post:{image,title}}=this.props;
return(
<Viewstyle={styles.main}ref="main">
<TouchableOpacity
onPress={this.onPressImage}
activeOpacity={0.9}
>
<Image
source={image}
style={styles.image}
resizeMode="cover"
/>
</TouchableOpacity>
<Textstyle={styles.title}>{title}</Text>
</View>
);
}
12. Asalways,weneedtodefinesomestylesforthiscomponent.Wearegoing
toaddsomecolorsandpadding,asfollows:
conststyles=StyleSheet.create({
main:{
backgroundColor:'#fff',
marginBottom:30,
paddingBottom:10,
},
content:{
flex:1,
},
image:{
width,
height:300,
},
title:{
margin:10,
color:'#ccc',
}
});
13. Ifweruntheappnow,weshouldbeabletoseethetimeline,howeverifwe
pressanyoftheimages,anerrorwillbethrown.Weneedtodefinethe
viewer,solet'sopenthePhotoViewer/index.jsfileandimportthe
dependencies:
importReact,{Component}from'react';
import{
Animated,
Dimensions,
Easing,
Text,
TouchableOpacity,
StyleSheet,
}from'react-native';
14. Let'sdefinepropsforthiscomponent.Inordertocentertheimageonthe
screen,weneedtoknowtheheightofthecurrentdevice:
const{width,height}=Dimensions.get('window');
exportdefaultclassPhotoViewerextendsComponent{
staticdefaultProps={
onClose:()=>{},
};
//Definedonfollowingsteps
}
15. Wewanttoruntwoanimationswhenshowingthiscomponent,sowe'll
needtoinitializeandruntheanimationafterthecomponentismounted.
Theanimationissimple:itjustgoesfrom0to1in400millisecondswith
someeasingapplied,asfollows:
componentWillMount(){
this.animatedValue=newAnimated.Value(0);
}
componentDidMount(){
Animated.timing(
this.animatedValue,
{
toValue:1,
duration:400,
easing:Easing.in,
}
).start();
}
16. Whentheuserpressestheclosebutton,weneedtoexecute
theonClosecallbacktoinformtheparentthatthiscomponentneedstobe
removed,asfollows:
onPressBtn=()=>{
this.props.onClose();
}
17. Wearegoingtosplittherendermethodintotwosteps.First,weneedto
interpolatethevaluesfortheanimations,asfollows:
render(){
const{post:{image,title},position}=this.props;
consttop=this.animatedValue.interpolate({
inputRange:[0,1],
outputRange:[position.pageY,height/2-position.height/2],
});
constopacity=this.animatedValue;
//Definedonnextstep
}
18. Weonlyneedtodefinethreeelements:Viewtoanimatethe
background,imagetodisplaytheimage,andaclosebutton.Wearesetting
theopacitystyletothemainview,whichwillanimatetheimagebackground
fromtransparenttoblack.Theimagewillslideinatthesametime,creating
aniceeffect,asfollows:
//Definedonpreviousstep
render(){
return(
<Animated.View
style={[
styles.main,
{opacity},
]}
>
<Animated.Image
source={image}
style={[
styles.image,
{top,opacity}
]}
/>
<TouchableOpacitystyle={styles.closeBtn}
onPress={this.onPressBtn}
>
<Textstyle={styles.closeBtnText}>X</Text>
</TouchableOpacity>
</Animated.View>
);
}
19. Wearealmostdone!Thelaststepinthisrecipeistodefinethestyles.We
needtosetthepositionofthemaincontainertoabsolutesothattheimage
isontopofeverythingelse.We'llalsomovetheclosebuttontothetop-right
ofthescreen,asfollows:
conststyles=StyleSheet.create({
main:{
backgroundColor:'#000',
bottom:0,
left:0,
position:'absolute',
right:0,
top:0,
},
image:{
width,
height:300,
},
closeBtn:{
position:'absolute',
top:50,
right:20,
},
closeBtnText:{
fontSize:20,
color:'#fff',
fontWeight:'bold',
},
});
20. Thefinalappshouldlooksimilartothefollowingscreenshot:
Howitworks...
Instep4,wedefinedtwopropertiesonstate:selectedandposition.
Theselectedpropertyholdstheimagedataforthepressedimage,whichcanbe
anyofthetimelineobjectsdefinedinstep3.Thepositionpropertywillholdthe
currenty-coordinateonthescreen,whichisusedlatertoanimatetheimagefrom
itsoriginalpositiontothecenterofthescreen.
Instep5,wemapoverthetimelinearraytorendereachpost.Weused
thePostContainerelementforeachpost,sendingthepostinformationandusing
theonPresscallbacktosetthepressedimage.
Instep10,weneedthecurrentpositionoftheimage.Toachievethis,weuse
themeasuremethodfromthecomponentwewanttogettheinformationfrom.
Thismethodreceivesacallbackfunctionandretrieves,amongother
properties,width,height,andthecurrentpositiononthescreen.
Weareusingareferencetoaccessthecomponent,declaredintheJSXonthe
nextstep.
Instep11,wedeclaredtheJSXforthecomponent.Inthemainwrapper
container,wesettherefproperty,whichisusedtogetthecurrentpositionofthe
image.Wheneverwewanttoaccessacomponentonanyofthemethodsofthe
currentclass,weuseareference.Wecancreatereferencesbysimplysetting
therefpropertyandassigninganametoanycomponent.
Instep18,weinterpolatetheanimationvaluestogetthecorrecttopvaluefor
eachframe.Theoutputofthatinterpolationwillstartfromthecurrentposition
oftheimageandprogresstothemiddleofthescreen.Thisway,dependingon
whetherthevaluesarenegativeorpositive,theanimationwillrunfrombottom
totop,ortheotherwayaround.
Wedon'tneedtointerpolateopacity,sincethecurrentanimatedvaluealready
goesfrom0to1.
Seealso
AnindepthexplanationofRefsandtheDOMcanbefoundatthefollowing
link:
https://reactjs.org/docs/refs-and-the-dom.html.
WorkingwithApplicationLogicand
Data
Inthischapter,wewillcoverthefollowingrecipes:
Storingandretrievingdatalocally
RetrievingdatafromaremoteAPI
SendingdatatoaremoteAPI
Establishingreal-timecommunicationwithWebSockets
IntegratingpersistentdatabasefunctionalitywithRealm
Maskingtheapplicationuponnetworkconnectionloss
SynchronizinglocallypersisteddatawitharemoteAPI
Introduction
Oneofthemostimportantaspectsofdevelopinganyapplicationishandling
data.Thisdatamaycomelocallyfromtheuser,maybeservedbyaremote
serverthatexposesanAPI,or,aswithmostbusinessapplications,maybesome
combinationofboth.Youmaybewonderingwhatstrategiesarebestfordealing
withdata,orhowtoevenaccomplishsimpletaskssuchasmakinganHTTP
request.Luckily,ReactNativemakesyourlifethatmuchsimplerbyproviding
mechanismsforeasilydealingwithdata.
Theopensourcecommunityhastakenthingsastepfurtherandprovidedsome
excellentmodulesthatcanbeusedwithReactNative.Inthischapter,wewill
discusshowtoworkwithdatainallaspects,andhowitintegratesintoourReact
Nativeapplications.
Storingandretrievingdatalocally
Whendevelopingamobileapp,weneedtoconsiderthenetworkchallengesthat
needtobeovercome.Awell-designedappshouldallowtheusertocontinue
usingtheappwhenthereisnointernetconnection.Thisrequirestheapptosave
datalocallyonthedevicewhenthere'snointernetconnection,andtoalsosync
thatdatawiththeserverwhenthenetworkisavailableagain.
Anotherchallengetoovercomeisnetworkconnectivity,whichmightbeslowor
limited.Toimprovetheperformanceofourapp,weshouldsavecriticaldataon
thelocaldevicetoavoidputtingstressonourserverAPI.
Inthisrecipe,wewilllearnaboutabasicandeffectivestrategyforsavingand
retrievingdatalocallyfromthedevice.Wewillcreateasimpleappwithatext
inputandtwobuttons,onetosavethecontent,ofthefieldandonetoloadthe
existingcontent.WewillusetheAsyncStorageclasstoachieveourgoal.
Gettingready
Weneedtocreateanemptyappnamedlocal-data-storage.
Howtodoit...
1. We'llbeginwiththeAppcomponent.Let'sstartbyimportingallofthe
dependencies:
importReact,{Component}from'react';
import{
Alert,
AsyncStorage,
StyleSheet,
Text,
TextInput,
TouchableOpacity,
View,
}from'react-native';
2. Now,let'screatetheAppclass.Wearegoingtocreateakeyconstantsothat
wecansetthenameofthekeywewillusetosavethecontent.Onthestate,
we'llhavetwoproperties:onetokeepthevaluefromthetextinput
component,andanothertoloadanddisplaythecurrentlystoredvalue:
constkey='@MyApp:key';
exportdefaultclassAppextendsComponent{
state={
text:'',
storedValue:'',
};
//Definedinlatersteps
}
3. Whenthecomponentmounts,wewanttoloadtheexistingstoredvalue–if
itexists.We'lldisplaythecontentoncetheapploads,sowe'llneedtoread
thelocalvalueinthecomponentWillMountlifecyclemethod:
componentWillMount(){
this.onLoad();
}
4. TheonLoadfunctionloadsthecurrentcontentfromthelocalstorage.Like
localStorageinthebrowser,it'saseasyasusingthekeywedefinedwhen
savingthedata:
onLoad=async()=>{
try{
conststoredValue=awaitAsyncStorage.getItem(key);
this.setState({storedValue});
}catch(error){
Alert.alert('Error','Therewasanerrorwhileloadingthe
data');
}
}
5. Savingthedataisstraightforwardaswell.We'lldeclareakeytosaveany
datawewanttoassociatewiththatkey,viathesetItemmethod
ofAsyncStorage:
onSave=async()=>{
const{text}=this.state;
try{
awaitAsyncStorage.setItem(key,text);
Alert.alert('Saved','Successfullysavedondevice');
}catch(error){
Alert.alert('Error','Therewasanerrorwhilesavingthe
data');
}
}
6. Next,weneedafunctionforsavingthevaluefromtheinputtexttothe
state.Whenthevalueoftheinputchanges,wewillgetthenewvalueand
saveittothestate:
onChange=(text)=>{
this.setState({text});
}
7. OurUIwillbesimple:justaTextelementtorenderthesavedcontent,a
TextInputcomponenttoallowtheusertoenteranewvalue,andtwobuttons.
OnebuttonwillcalltheonLoadfunctiontoloadthecurrentsavedvalue,and
theotherwillsavethevaluefromthetextinput:
render(){
const{storedValue,text}=this.state;
return(
<Viewstyle={styles.container}>
<Textstyle={styles.preview}>{storedValue}</Text>
<View>
<TextInput
style={styles.input}
onChangeText={this.onChange}
value={text}
placeholder="Typesomethinghere..."
/>
<TouchableOpacityonPress={this.onSave}style=
{styles.button}>
<Text>Savelocally</Text>
</TouchableOpacity>
<TouchableOpacityonPress={this.onLoad}style=
{styles.button}>
<Text>Loaddata</Text>
</TouchableOpacity>
</View>
</View>
);
}
8. Finally,let'saddsomestyles.Thiswillbesimplecolors,paddings,margins,
andalayout,ascoveredinChapter2,CreatingaSimpleReactNativeApp:
conststyles=StyleSheet.create({
container:{
flex:1,
justifyContent:'center',
alignItems:'center',
backgroundColor:'#fff',
},
preview:{
backgroundColor:'#bdc3c7',
width:300,
height:80,
padding:10,
borderRadius:5,
color:'#333',
marginBottom:50,
},
input:{
backgroundColor:'#ecf0f1',
borderRadius:3,
width:300,
height:40,
padding:5,
},
button:{
backgroundColor:'#f39c12',
padding:10,
borderRadius:3,
marginTop:10,
},
});
9. Thefinalappshouldlooksimilartothefollowingscreenshot:
Howitworks...
TheAsyncStorageclassallowsustoeasilysavedataonthelocaldevice.OniOS,
thisisaccomplishedbyusingdictionariesontextfiles.OnAndroid,itwilluse
RocksDBorSQLite,dependingonwhat'savailable.
It'snotrecommendedtosavesensitiveinformationusingthismethod,asthedataisnot
encrypted.
Instep4,weloadedthecurrentsaveddata.TheAsyncStorageAPIcontainsa
getItemmethod.Thismethodreceivesthekeywewanttoretrieveasaparameter.
Weareusingtheawait/asyncsyntaxheresincethiscallisasynchronous.Afterwe
getthevalue,wejustsetittostate;thisway,wewillbeabletorenderthedata
ontheview.
Instep7,wesavedthetextfromthestate.UsingthesetItemmethod,wecanseta
newkeywithanyvaluewewant.Thiscallisasynchronous,thereforeweused
theawait/asyncsyntax.YoucanchecktheFurtherreadingsectionofthischapter
foragreatarticlethatexplainstheawait/asyncsyntaxindepth.
Seealso
Agreatarticleonhowasync/awaitinJavaScriptworks,availableathttps://ponyfoo.
com/articles/understanding-javascript-async-await.
RetrievingdatafromaremoteAPI
Inthepreviouschapters,weusedthedatafromaJSONfileordirectlydefinedin
thesourcecode.Whilethatworkedforourpreviousrecipes,it'srarelyvery
helpfulinreal-worldapplications.
Inthisrecipe,wewilllearnhowtorequestdatafromanAPI.WewillfetchaGET
requestfromanAPItogetaJSONresponse.Fornow,however,weareonly
goingtodisplaytheJSONinatextelement.We'llbeusingtheFakeOnline
RESTAPIforTestingandPrototyping,hostedat
http://jsonplaceholder.typicode.comandpoweredbytheexcellentdevelopmenttest
APIsoftware,JSONServer(https://github.com/typicode/json-server).
Wewillkeepthisappsimplesothatwecanfocusondatamanagement.Wewill
haveatextcomponentthatwilldisplaytheresponsefromtheAPIandalsoadda
buttonthatrequeststhedatawhenpressed.
Gettingready
Weneedtocreateanemptyapp.Let'snamethisoneremote-api.
Howtodoit...
1. Let'sstartbyimportingourdependenciesintotheApp.jsfile:
importReact,{Component}from'react';
import{
StyleSheet,
Text,
TextInput,
TouchableOpacity,
View
}from'react-native';
2. Wearegoingtodefinearesultspropertyonthestate.Thispropertywill
holdtheresponsefromtheAPI.We'llneedtoupdatetheviewonceweget
theresponse:
exportdefaultclassAppextendsComponent{
state={
results:'',
};
//Definedlater
}
conststyles=StyleSheet.create({
//Definedlater
});
3. We'llsendtherequestwhenthebuttonispressed.Next,let'screatea
methodtohandlethatrequest:
onLoad=async()=>{
this.setState({results:'Loading,pleasewait...'});
constresponse=awaitfetch('http://jsonplaceholder.typicode.com/users',{
method:'GET',
});
constresults=awaitresponse.text();
this.setState({results});
}
4. Intherendermethod,we'lldisplaytheresponse,whichwillbereadfromthe
state.WewilluseaTextInputtodisplaytheAPIdata.Viaproperties,we'll
declareeditingasdisabledandsupportmultilinefunctionality.Thebutton
willcalltheonLoadfunctionthatwecreatedinthepreviousstep:
render(){
const{results}=this.state;
return(
<Viewstyle={styles.container}>
<View>
<TextInput
style={styles.preview}
value={results}
placeholder="Results..."
editable={false}
multiline
/>
<TouchableOpacityonPress={this.onLoad}style=
{styles.btn}>
<Text>Loaddata</Text>
</TouchableOpacity>
</View>
</View>
);
}
5. Finally,we'lladdsomestyles.Again,thiswilljustbethelayout,colors,
margins,andpadding:
conststyles=StyleSheet.create({
container:{
flex:1,
justifyContent:'center',
alignItems:'center',
backgroundColor:'#fff',
},
preview:{
backgroundColor:'#bdc3c7',
width:300,
height:400,
padding:10,
borderRadius:5,
color:'#333',
marginBottom:50,
},
btn:{
backgroundColor:'#3498db',
padding:10,
borderRadius:3,
marginTop:10,
},
});
6. Thefinalappshouldlooksimilartothefollowingscreenshot:
Howitworks...
Instep4,wesenttherequesttotheAPI.Weusethefetchmethodtomakethe
request.ThefirstparameterisastringwiththeURLoftheendpoint,while
thesecondparameterisaconfigurationobject.Forthisrequest,theonlyoption
weneedtodefineistherequestmethodtoGET,butwecanalsousethisobjectto
defineheaders,cookies,parameters,andmanyotherthings.
Wearealsousingasync/awaitsyntaxtowaitontheresponseandfinallysetiton
thestate.Ifyouprefer,youcould,ofcourse,usepromisesforthispurpose
instead.
Also,notehowweareusinganarrowfunctionheretoproperlyhandlethe
scope.Thiswillautomaticallysetthecorrectscopewhenthismethodisassigned
totheonPresscallback.
SendingdatatoaremoteAPI
Inthepreviousrecipe,wecoveredhowtogetdatafromanAPIusingfetch.In
thisrecipe,wewilllearnhowtoPOSTdatatothesameAPI.Thisappwillemulate
creatingaforumpost,andtherequestforthepostwillhavetitle,body,anduser
parameters.
Gettingready
Beforegoingthroughthisrecipe,weneedtocreateanewemptyappnamed
remote-api-post.
Inthisrecipe,wewillalsobeusingtheverypopularaxiospackageforhandling
ourAPIrequests.YoucaninstallitviatheTerminalwithyarn:
yarnaddaxios
Alternatively,youcanusenpm:
npminstallaxios--save
Howtodoit...
1. First,we'llneedtoopentheApp.jsfileandimportthedependencieswe'llbe
using:
importReact,{Component}from'react';
importaxiosfrom'axios';
import{
Alert,
ScrollView,
StyleSheet,
Text,
TextInput,
TouchableOpacity,
SafeAreaView,
}from'react-native';
2. We'lldefinetheAppclasswithastateobjectthathasthreeproperties.The
titleandbodypropertieswillbeusedformakingtherequest,andresultswill
holdtheAPI'sresponse:
constendpoint='http://jsonplaceholder.typicode.com/posts';
exportdefaultclassAppextendsComponent{
state={
results:'',
title:'',
body:'',
};
conststyles=StyleSheet.create({
//Definedlater
});
}
3. Aftersavinganewpost,wewillrequestallofthepostsfromtheAPI.We
aregoingtodefineanonLoadmethodtofetchthenewdata.Thiscodeworks
justthesameastheonLoadmethodinthepreviousrecipe,butthistime,we'll
beusingtheaxiospackagetocreatetherequest:
onLoad=async()=>{
this.setState({results:'Loading,pleasewait...'});
constresponse=awaitaxios.get(endpoint);
constresults=JSON.stringify(response);
this.setState({results});
}
4. Let'sworkonsavingthenewdata.First,weneedtogetthevaluesfromthe
state.Wecouldalsorunsomevalidationsheretomakesurethatthetitle
andbodyarenotempty.OnthePOSTrequest,weneedtodefinethecontent
typeoftherequest,which,inthiscase,willbeJSON.Wewillhardcodethe
userIdpropertyto1.Inarealapp,wewouldhaveprobablygottenthisvalue
fromapreviousAPIrequest.Aftertherequesthascompleted,wegetthe
JSONresponse,which,ifsuccessful,willfiretheonLoadmethodthatwe
definedpreviously:
onSave=async()=>{
const{title,body}=this.state;
try{
constresponse=awaitaxios.post(endpoint,{
headers:{
'Content-Type':'application/json;charset=UTF-8',
},
params:{
userId:1,
title,
body
}
});
constresults=JSON.stringify(response);
Alert.alert('Success','Postsuccessfullysaved');
this.onLoad();
}catch(error){
Alert.alert('Error',`Therewasanerrorwhilesavingthe
post:${error}`);
}
}
5. Thesavefunctionalityiscomplete.Next,weneedmethodsforsavingthe
titleandbodytothestate.Thesemethodswillbeexecutedastheusertypes
intheinputtext,keepingtrackofthevaluesonthestateobject:
onTitleChange=(title)=>this.setState({title});
onPostChange=(body)=>this.setState({body});
6. Wehaveeverythingweneedforthefunctionality,solet'saddtheUI.The
rendermethodwilldisplayatoolbar,twoinputtexts,andaSavebuttonfor
callingtheonSavemethodthatwedefinedinstep4:
render(){
const{results,title,body}=this.state;
return(
<SafeAreaViewstyle={styles.container}>
<Textstyle={styles.toolbar}>Addanewpost</Text>
<ScrollViewstyle={styles.content}>
<TextInput
style={styles.input}
onChangeText={this.onTitleChange}
value={title}
placeholder="Title"
/>
<TextInput
style={styles.input}
onChangeText={this.onPostChange}
value={body}
placeholder="Postbody..."
/>
<TouchableOpacityonPress={this.onSave}style=
{styles.button}>
<Text>Save</Text>
</TouchableOpacity>
<TextInput
style={styles.preview}
value={results}
placeholder="Results..."
editable={false}
multiline
/>
</ScrollView>
</SafeAreaView>
);
}
7. Finally,let'saddthestylestodefinethelayout,color,padding,andmargins:
conststyles=StyleSheet.create({
container:{
flex:1,
backgroundColor:'#fff',
},
toolbar:{
backgroundColor:'#3498db',
color:'#fff',
textAlign:'center',
padding:25,
fontSize:20,
},
content:{
flex:1,
padding:10,
},
preview:{
backgroundColor:'#bdc3c7',
flex:1,
height:500,
},
input:{
backgroundColor:'#ecf0f1',
borderRadius:3,
height:40,
padding:5,
marginBottom:10,
flex:1,
},
button:{
backgroundColor:'#3498db',
padding:10,
borderRadius:3,
marginBottom:30,
},
});
8. Thefinalappshouldlooksimilartothefollowingscreenshot:
Howitworks...
Instep2,wedefinedthreepropertiesonthestate.Theresultspropertywill
containtheresponsefromtheserverAPI,whichwelaterusetodisplaythevalue
intheUI.
Weusedthetitleandbodypropertiestoholdthevaluesfromtheinputtext
componentssothattheusercancreateanewpost.Thosevalueswillthenbesent
totheAPIwhenpressingtheSavebutton.
Instep6,wedeclaredtheelementsontheUI.Weusedtwoinputsforpostdata
andtheSavebutton,whichcallstheonSavemethodwhenpressed.Finally,we
usedinputtexttodisplaytheresult.
Establishingreal-time
communicationwithWebSockets
Inthisrecipe,wewillintegrateWebSocketsinaReactNativeapplication.We
aregoingtousetheHelloWorldofWebSocketsapplications,thatis,asimple
chatapp.Thisappwillallowuserstosendandreceivemessages.
Gettingready
TosupportWebSocketsonReactNative,wewillneedtorunaservertohandle
allconnectedclients.Theservershouldbeabletobroadcastamessagewhenit
receivesamessagefromanyoftheconnectedclients.
We'llstartwithanew,emptyReactNativeapp.We'llnameitweb-sockets.Inthe
rootoftheproject,let'saddaserverfolderwithanindex.jsfileinsideofit.Ifyou
don'talreadyhaveit,you'llneedNodetoruntheserver.YoucangetNode.js
fromhttps://nodejs.org/orbyusingtheNodeVersionManager(https://github.com/
creationix/nvm).
We'llbeusingtheexcellentWebSocketpackage,ws.Youcanaddthepackagevia
theTerminalwithyarn:
yarnaddws
Alternatively,youcanusenpm:
npminstall--savews
Onceyou'vegotthepackageinstalled,addthefollowingcodetothe
/server/index.jsfile.Oncethisserverisrunning,itwilllistenforincoming
connectionsviaserver.on('connection')andincomingmessagesvia
socket.on('message').Formoreinformationonhowwsworks,youcancheckoutthe
documentationathttps://github.com/websockets/ws:
constport=3001;
constWebSocketServer=require('ws').Server;
constserver=newWebSocketServer({port});
server.on('connection',(socket)=>{
socket.on('message',(message)=>{
console.log('received:%s',message);
server.clients.forEach(client=>{
if(client!==socket){
client.send(message);
}
});
});
});
console.log(`WebSocketServerrunningonport${port}`);
Oncetheservercodeisinplace,youcanstartuptheserverusingNodeby
runningthefollowingcommandintheTerminalattherootoftheproject:
nodeserver/index.js
Leavetheserverrunningsothat,oncewe'vebuilttheReactNativeapp,wecan
usetheservertocommunicatebetweenclients.
Howtodoit...
1. First,let'screatetheApp.jsfileandimportallthedependencieswe'llbe
using:
importReact,{Component}from'react';
import{
Dimensions,
ScrollView,
StyleSheet,
Text,
TextInput,
SafeAreaView,
View,
Platform
}from'react-native';
2. Onthestateobject,we'lldeclareahistoryproperty.Thispropertywillbean
arrayforholdingallofthemessagesthathavebeensentbackandforth
betweenusers:
exportdefaultclassAppextendsComponent{
state={
history:[],
};
//Definedinlatersteps
}
conststyles=StyleSheet.create({
//Definedinlatersteps
});
3. Now,weneedtointegrateWebSocketsintoourappbyconnectingtothe
serverandsettingupthecallbackfunctionsforreceivingmessages,errors,
andwhentheconnectionisopenedorclosed.Wewilldothiswhenthe
componenthasbeencreated,byusingthecomponentWillMountlifecyclehook:
componentWillMount(){
constlocalhost=Platform.OS==='android'?'10.0.3.2':
'localhost';
this.ws=newWebSocket(`ws://${localhost}:3001`);
this.ws.onopen=this.onOpenConnection;
this.ws.onmessage=this.onMessageReceived;
this.ws.onerror=this.onError;
this.ws.onclose=this.onCloseConnection;
}
Youwillnoticethatwearedeclaringlocalhosttoequal10.0.3.2iftheappisbeingrunonan
Androiddevice.Genymotionhasportblockingenabledbydefaultonlocalhostconnections,
butalsoexposestheIPaddress10.0.3.2toallowportforwardingtolocalhost.Thisissolelya
workaroundforGenymotion'sportblocking.ChecktheThere'smore...sectionattheendof
thisrecipeformoreinformation.
4. Let'sdefinethecallbacksforopened/closedconnectionsandforhandling
receivederrors.Wearejustgoingtologtheactions,butthisiswherewe
couldshowanalertmessagewhentheconnectionisclosed,ordisplayan
errormessagewhenanerroristhrownbytheserver:
onOpenConnection=()=>{
console.log('Open!');
}
onError=(event)=>{
console.log('onerror',event.message);
}
onCloseConnection=(event)=>{
console.log('onclose',event.code,event.reason);
}
5. Whenreceivinganewmessagefromtheserver,weneedtoaddittothe
historypropertyonthestatesothatwecanrenderthenewcontentassoon
asitarrives:
onMessageReceived=(event)=>{
this.setState({
history:[
...this.state.history,
{isSentByMe:false,messageText:event.data},
],
});
}
6. Now,ontosendingthemessage.Weneedtodefineamethodthatwillget
executedwhentheuserpressestheReturnkeyonthekeyboard.Weneedto
dotwothingsatthispoint:addthenewmessagetohistory,andthensend
themessagethroughthesocket:
onSendMessage=()=>{
const{text}=this.state;
this.setState({
text:'',
history:[
...this.state.history,
{isSentByMe:true,messageText:text},
],
});
this.ws.send(text);
}
7. Inthepreviousstep,wegotthetextpropertyfromthestate.Weneedto
keeptrackofthevaluewhenevertheusertypessomethingintotheinput,so
we'llneedafunctionforlisteningtokeystrokesandsavingthevalue
tostate:
onChangeText=(text)=>{
this.setState({text});
}
8. Wehaveallofthefunctionalityinplace,solet'sworkontheUI.Inthe
rendermethod,we'lladdatoolbar,ascrollviewtorenderallofthemessages
inhistory,andatextinputtoallowtheusertosendanewmessage:
render(){
const{history,text}=this.state;
return(
<SafeAreaViewstyle={[styles.container,android]}>
<Textstyle={styles.toolbar}>SimpleChat</Text>
<ScrollViewstyle={styles.content}>
{history.map(this.renderMessage)}
</ScrollView>
<Viewstyle={styles.inputContainer}>
<TextInput
style={styles.input}
value={text}
onChangeText={this.onChangeText}
onSubmitEditing={this.onSendMessage}
/>
</View>
</SafeAreaView>
);
}
9. Torenderthemessagesfromhistory,we'llloopthroughthehistoryarrayand
rendereachmessageviatherenderMessagemethod.We'llneedtocheck
whetherthecurrentmessagebelongstotheuseronthisdevicesothatwe
canapplytheappropriatestyles:
renderMessage(item,index){
constsender=item.isSentByMe?styles.me:styles.friend;
return(
<Viewstyle={[styles.msg,sender]}key={index}>
<Text>{item.msg}</Text>
</View>
);
}
10. Finally,let'sworkonthestyles!Let'saddstylestothetoolbar,thehistory
component,andthetextinput.Weneedtosetthehistorycontaineras
flexible,sincewewantittotakeupalloftheavailableverticalspace:
conststyles=StyleSheet.create({
container:{
backgroundColor:'#ecf0f1',
flex:1,
},
toolbar:{
backgroundColor:'#34495e',
color:'#fff',
fontSize:20,
padding:25,
textAlign:'center',
},
content:{
flex:1,
},
inputContainer:{
backgroundColor:'#bdc3c7',
padding:5,
},
input:{
height:40,
backgroundColor:'#fff',
},
//Definedinnextstep
});
11. Now,ontothestylesforeachmessage.Wearegoingtocreateacommon
stylesobjectcalledmsgforallmessages,thenstylesformessagesfromthe
useronthedevice,andfinally,stylesformessagesfromothers,changing
thecolorandalignmentaccordingly:
msg:{
margin:5,
padding:10,
borderRadius:10,
},
me:{
alignSelf:'flex-start',
backgroundColor:'#1abc9c',
marginRight:100,
},
friend:{
alignSelf:'flex-end',
backgroundColor:'#fff',
marginLeft:100,
}
12. Thefinalappshouldlooksimilartothefollowingscreenshot:
Howitworks...
Instep2,wedeclaredthestateobjectwithahistoryarrayforkeepingtrackof
messages.Thehistorypropertywillholdobjectsrepresentingallofthemessages
beingexchangedbetweenclients.Eachobjectwillhavetwoproperties:astring
withthemessagetext,andaBooleanflagtodeterminethesender.Wecouldadd
moredatahere,suchasthenameoftheuser,aURLoftheavatarimage,
oranythingelsewemightneed.
Instep3,weconnectedtothesocketprovidedbytheWebSocketserverandset
upcallbacksforhandlingsocketevents.Wespecifiedtheserveraddressaswell
astheport.
Instep5,wedefinedthecallbacktoexecutewhenanewmessageisreceived
fromtheserver.Here,weaddanewobjecttothehistoryarrayonthestateevery
timeanewmessageisreceived.Eachmessageobjecthastheproperties
isSentByMeandmessageTest.
Instep6,wesentthemessagetotheserver.Weneedtoaddthemessagetothe
historybecausetheserverwillbroadcastthemessagetoallotherclients,butnot
theauthorofthemessage.Tokeeptrackofthismessage,weneedtomanually
addittothehistory.
There'smore...
AsmentionedintheTipfollowingstep3,Genymotionblocksportforwarding
bydefault.Ifwejustusedlocalhostastheserveraddress,itwouldnotwork
properlywhentheappwasrunninginGenymotion.Theeasiestworkaroundisto
usetheIPaddress10.0.3.2tobypassthisproblem.Moreinformationcanbe
foundatthefollowingStackOverflowpost,whichcanbefoundathttps://stackov
erflow.com/questions/20257266/how-to-access-localhost-from-a-genymotion-android-emulator.
YoucouldalsousetheIPaddressforyourlocalmachine,whichwouldalsofix
thelocalhostportissueinAndroid.FormoreinformationonlocatingtheIP
addressofyourMac,gotohttp://osxdaily.com/2010/11/21/find-ip-address-mac/.
Integratingpersistentdatabase
functionalitywithRealm
Asyourapplicationbecomesmorecomplex,youwilllikelyreachapointwhere
youneedtostoredataonthedevice.Thiscouldbebusinessdata,suchasuser
lists,toavoidhavingtomakeexpensivenetworkconnectionstoaRemoteAPI.
Maybeyoudon'thaveanAPIatallandyourapplicationworksasaself-
sufficiententity.Regardlessofthesituation,youmaybenefitfromleveraginga
databasetostoreyourdata.TherearemultipleoptionsforReactNative
applications.ThefirstoptionisAsyncStorage,whichwecoveredintheStoringand
retrievingdatalocallyrecipeinthischapter.YoucouldalsoconsiderSQLite,or
youcouldwriteanadaptertoanOS-specificdataprovider,suchasCoreData.
Anotherexcellentoptionisusingamobiledatabase,suchasRealm.Realmisan
extremelyfast,thread-safe,transactional,object-baseddatabase.Itisprimarily
designedforusebymobiledevices,withastraightforwardJavaScriptAPI.It
supportsotherfeatures,suchasencryption,complexquerying,UIbindings,and
more.Youcanreadallaboutitathttps://realm.io/products/realm-mobile-database/.
Inthisrecipe,wewillwalkthroughusingRealminReactNative.Wewillcreate
asimpledatabaseandperformbasicoperations,suchasinserting,updating,and
deletingrecords.WewillthendisplaytheserecordsintheUI.
Gettingready
Let'screateanewemptyReactNativeappnamedrealm-db.
InstallingRealmrequiresrunningthefollowingcommand:
react-nativelink
Becauseofthis,wewillbeworkingonanappthatisdetachedfromExpo.This
meansthatyoucouldcreatethisappwiththefollowingcommand:
react-nativeinit
Alternatively,youcouldcreateanewExpoappwiththefollowingcommand:
expoinit
Then,youcanejecttheappthatwascreatedwithExpoviathefollowing
command:
expoeject
Onceyou'vecreatedaReactNativeapp,besuretoinstalltheCocoaPods
dependenciesviatheiosdirectorybyusingcdinsidethenewappandrunningthe
following:
podinstall
RefertoChapter10,AppWorkflowandThird-partyPlugins,forain-depth
explanationofhowCocoaPodsworks,andhowejected(orpureReactNative)
applicationsdifferfromExpoReactNativeapplications.
IntheSendingdatatoaremoteAPIrecipe,wehandledourAJAXcallswith
theaxiospackage.Inthisrecipe,wewillbeusingthenativeJavaScriptfetch
methodforAJAXcalls.Eithermethodworksjustaswell,andhavingexposure
tobothwillhopefullyallowyoutodecidewhichyoupreferforyourprojects.
Onceyou'vetakencareofcreatinganejectedapp,installRealmwithyarn:
yarnaddrealm
Alternatively,youcanusenpm:
npminstall--saverealm
Withthepackageinstalled,youcanlinkthenativepackageswiththefollowing
code:
react-nativelinkrealm
Howtodoit...
1. First,let'sopenApp.jsandimportthedependencieswe'llbeusing:
importReact,{Component}from'react';
import{
StyleSheet,
Text,
View,
TouchableOpacity
}from'react-native';
importRealmfrom'realm';
2. Next,weneedtoinstantiateourRealmdatabase,whichwe'lldointhe
componentWillMountmethod.We'llkeepareferencetoitbyusingtherealmclass
variable:
exportdefaultclassAppextendsComponent{
realm;
componentWillMount(){
constrealm=this.realm=newRealm({
schema:[
{
name:'User',
properties:{
firstName:'string',
lastName:'string',
email:'string'
}
}
]
});
}
//Definedinlatersteps.
}
3. TocreatetheUserentries,wewillusetherandomusergeneratorAPI
providedbyrandomuser.me.Let'screateamethodwiththegetRandomUser
function.Thiswillfetchthisdata:
getRandomUser(){
returnfetch('https://randomuser.me/api/')
.then(response=>response.json());
}
4. We'llalsoneedamethodforcreatingusersinourapp.ThecreateUser
methodwillusethefunctionwedefinedpreviouslytogetarandomuser,
beforesavingittoourrealmdatabasewiththerealm.writemethodand
therealm.createmethod:
createUser=()=>{
constrealm=this.realm;
this.getRandomUser().then((response)=>{
constuser=response.results[0];
constuserName=user.name;
realm.write(()=>{
realm.create('User',{
firstName:userName.first,
lastName:userName.last,
email:user.email
});
this.setState({users:realm.objects('User')});
});
});
}
5. Sincewe'reinteractingwithadatabase,weshouldalsoaddafunctionfor
updatingaUserinthedatabase.updateUserwill,forsimplicity,takethefirst
recordinthecollectionandchangeitsinformation:
updateUser=()=>{
constrealm=this.realm;
constusers=realm.objects('User');
realm.write(()=>{
if(users.length){
letfirstUser=users.slice(0,1)[0];
firstUser.firstName='Bob';
firstUser.lastName='Cookbook';
firstUser.email='react.native@cookbook.com';
this.setState(users);
}
});
}
6. Finally,let'saddawaytodeleteourusers.We'lladdadeleteUsersmethodfor
removingallusers.Thisisachievedbycallingrealm.writewithacallback
functionthatexecutesrealm.deleteAll:
deleteUsers=()=>{
constrealm=this.realm;
realm.write(()=>{
realm.deleteAll();
this.setState({users:realm.objects('User')});
});
}
7. Let'sbuildourUI.WewillrenderalistofUserobjectsandabuttonforeach
ofourcreate,update,anddeletemethods:
render(){
constrealm=this.realm;
return(
<Viewstyle={styles.container}>
<Textstyle={styles.welcome}>
WelcometoRealmDBTest!
</Text>
<Viewstyle={styles.buttonContainer}>
<TouchableOpacitystyle={styles.button}
onPress={this.createUser}>
<Textstyle={styles.buttontext}>AddUser</Text>
</TouchableOpacity>
<TouchableOpacitystyle={styles.button}
onPress={this.updateUser}>
<Text>UpdateFirstUser</Text>
</TouchableOpacity>
<TouchableOpacitystyle={styles.button}
onPress={this.deleteUsers}>
<Text>RemoveAllUsers</Text>
</TouchableOpacity>
</View>
<Viewstyle={styles.container}>
<Textstyle={styles.welcome}>Users:</Text>
{this.state.users.map((user,idx)=>{
return<Textkey={idx}>{user.firstName}{user.lastName}
{user.email}</Text>;
})}
</View>
</View>
);
}
8. Onceweruntheapponeitherplatform,ourthreebuttonsforinteracting
withthedatabaseshoulddisplayoverthelivedatathat'ssavedinourRealm
database:
Howitworks...
TheRealmdatabaseisbuiltinC++anditscoreisknownastheRealmObject
Store.Thereareproductsthatencapsulatethisobjectstoreforeachmajor
platform(Java,Objective-C,Swift,Xamarin,andReactNative).TheReact
NativeimplementationisaJavaScriptadapterforRealm.FromtheReactNative
side,wedonotneedtoworryabouttheimplementationdetails.Instead,wegeta
cleanAPIforpersistingandretrievingdata.Thestep5tostep7demonstrate
usingsomebasicRealmmethods.Ifyouwanttoseemoreofwhatyoucando
withtheAPI,checkoutthedocumentationforthis,whichcanbefoundathttps:/
/realm.io/docs/react-native/latest/api/.
Maskingtheapplicationupon
networkconnectionloss
Aninternetconnectionisnotalwaysavailable,especiallywhenpeopleare
movingaroundacity,onthetrain,orhikinginthemountains.Agooduser
experiencewillinformtheuserwhentheirconnectiontotheinternethasbeen
lost.
Inthisrecipe,wewillcreateanappthatshowsamessagewhennetwork
connectionislost.
Gettingready
Weneedtocreateanemptyapp.Let'snameitnetwork-loss.
Howtodoit...
1. Let'sstartbyimportingthenecessarydependenciesintoApp.js:
importReact,{Component}from'react';
import{
SafeAreaView,
NetInfo,
StyleSheet,
Text,
View,
Platform
}from'react-native';
2. Next,we'lldefinetheAppclassandastateobjectforstoringtheconnectivity
status.TheonlineBooleanwillbetrueifconnected,andtheofflineBoolean
willbetrueifitisn't:
exportdefaultclassAppextendsComponent{
state={
online:null,
offline:null,
};
//Definedinlatersteps
}
3. Afterthecomponenthasbeencreated,weneedtogettheinitialnetwork
status.WearegoingtousetheNetInfoclass'sgetConnectionInfomethodtoget
thecurrentstatus,andwe'llalsosetupacallbackthat'sgoingtobe
executedwhenthestatuschanges:
componentWillMount(){
NetInfo.getConnectionInfo().then((connectionInfo)=>{
this.onConnectivityChange(connectionInfo);
});
NetInfo.addEventListener('connectionChange',
this.onConnectivityChange);
}
4. Whenthecomponentisabouttobedestroyed,weneedtoremovethe
listenerviathecomponentWillUnmountlifecycle:
componentWillUnmount(){
NetInfo.removeEventListener('connectionChange',
this.onConnectivityChange);
}
5. Let'saddthecallbackthatgetsexecutedwhenthenetworkstatuschanges.
Itjustcheckswhetherthecurrentnetworktypeisnone,andsetsthe
stateaccordingly:
onConnectivityChange=connectionInfo=>{
this.setState({
online:connectionInfo.type!=='none',
offline:connectionInfo.type==='none',
});
}
6. Now,weknowwhenthenetworkisonoroff,butwestillneedaUIfor
displayinginformation.Let'srenderatoolbarwithsomedummytextasthe
content:
render(){
return(
<SafeAreaViewstyle={styles.container}>
<Textstyle={styles.toolbar}>MyAwesomeApp</Text>
<Textstyle={styles.text}>Lorem...</Text>
<Textstyle={styles.text}>Loremipsum...</Text>
{this.renderMask()}
</SafeAreaView>
);
}
7. Asyoucanseefromthepreviousstep,there'sarenderMaskfunction.This
functionwillreturnamodalwhenthenetworkisoffline,andnothingifit's
online:
renderMask(){
if(this.state.offline){
return(
<Viewstyle={styles.mask}>
<Viewstyle={styles.msg}>
<Textstyle={styles.alert}>Seemslikeyoudonothave
networkconnectionanymore.</Text>
<Textstyle={styles.alert}>Youcanstillcontinue
usingtheapp,withlimitedcontent.</Text>
</View>
</View>
);
}
}
8. Finally,let'saddthestylesforourapp.We'llstartwiththetoolbarand
content:
conststyles=StyleSheet.create({
container:{
flex:1,
backgroundColor:'#F5FCFF',
},
toolbar:{
backgroundColor:'#3498db',
padding:15,
fontSize:20,
color:'#fff',
textAlign:'center',
},
text:{
padding:10,
},
//Definedinnextstep
}
9. Forthedisconnectionmessage,wewillrenderadarkmaskontopofall
content,andacontainerwiththetextatthecenterofthescreen.Forthe
mask,weneedtosetthepositiontoabsolute,andthensetthetop,bottom,right,
andleftto0.We'llalsoaddopacitytothemask'sbackgroundcolor,and
justifyandalignthecontenttothecenter:
conststyles=StyleSheet.create({
//Definedinpreviousstep
mask:{
alignItems:'center',
backgroundColor:'rgba(0,0,0,0.5)',
bottom:0,
justifyContent:'center',
left:0,
position:'absolute',
top:0,
right:0,
},
msg:{
backgroundColor:'#ecf0f1',
borderRadius:10,
height:200,
justifyContent:'center',
padding:10,
width:300,
},
alert:{
fontSize:20,
textAlign:'center',
margin:5,
}
});
10. Toseethemaskdisplayedintheemulators,theemulateddevicemustbe
disconnectedfromtheinternet.FortheiOSsimulator,simplydisconnect
yourMac'sWi-FiorunplugtheEthernettodisconnectthesimulatorfrom
theinternet.OnGenymotion,youcandisabletheWi-Ficonnectionofthe
phoneviathetoolbar:
11. Oncethedevicehasbeendisconnectedfromtheinternet,themaskshould
displayaccordingly:
Howitworks...
Instep2,wecreatedtheinitialstateobjectwithtwoproperties:onlinewillbetrue
whenanetworkconnectionisavailable,andofflinewillbetruewhenit'snot
available.
Instep3,weretrievedtheinitialnetworkstatusandsetupalistenertocheck
whenthestatuschanges.ThenetworktypereturnedbyNetInfowillbeeitherwifi,
cellular,unknown,ornone.Androidalsohastheextraoptionsofbluetooth,ethernet,
andWiMAX(forWiMAXconnections).Youcanreadthedocumentationtosee
alloftheavailablevalues:https://facebook.github.io/react-native/docs/netinfo.html.
Instep5,wedefinedthemethodthatwillexecutewheneverthenetworkstatus
changes,andsetthestatevaluesofonlineandofflineaccordingly.Updatingthe
statere-renderstheDOM,andthemaskisdisplayedifthereisnoconnection.
Synchronizinglocallypersisteddata
witharemoteAPI
Whenusingamobileapp,networkconnectivityissomethingthatisoftentaken
forgranted.ButwhathappenswhenyourappneedstomakeanAPIcall,andthe
userhasjustlostconnectivity?Fortunatelyforus,ReactNativehasamodule
thatreactstothenetworkconnectivitystatus.Wecanarchitectourapplicationin
awaythatsupportsthelossofconnectivitybysynchronizingourdata
automaticallyassoonasthenetworkconnectionisrestored.
ThisrecipewillshowasimpleimplementationofusingtheNetInfomoduleto
controlwhetherornotourapplicationwillmakeanAPIcall.Ifconnectivityis
lost,wewillkeepareferenceofthependingrequestandcompleteitwhenthe
networkaccessisrestored.Wewillbe
usinghttp://jsonplaceholder.typicode.comagaintomakeaPOSTrequesttoalive
server.
Gettingready
Forthisrecipe,wewilluseanemptyReactNativeapplicationnamedsyncing-
data.
Howtodoit...
1. We'llstartthisrecipebyimportingourdependenciesintoApp.js:
importReactfrom'react';
import{
StyleSheet,
Text,
View,
NetInfo,
TouchableOpacity
}from'react-native';
2. We'llneedtoaddthependingSyncclassvariable,whichwe'lluseforstoringa
pendingrequestwhenthereisnonetworkconnectionavailable.We'llalso
createthestateobjectwithpropertiesfortrackingwhethertheappis
connected(isConnected),thestatusofasync(syncStatus),andtheresponse
fromtheserverafterourPOSTrequestismade(serverResponse):
exportdefaultclassAppextendsReact.Component{
pendingSync;
state={
isConnected:null,
syncStatus:null,
serverResponse:null
}
//Definedinlatersteps
}
3. InthecomponentWillMountlifecyclehook,we'llgetthestatusofthenetwork
connectionviatheNetInfo.isConnected.fetchmethod,settingthe
state'sisConnectedpropertywiththeresponse.We'llalsoaddaneventlistener
totheconnectionChangeeventforkeepingtrackofchangestotheconnection:
componentWillMount(){
NetInfo.isConnected.fetch().then(isConnected=>{
this.setState({isConnected});
});
NetInfo.isConnected.addEventListener('connectionChange',
this.onConnectionChange);
}
4. Next,let'simplementthecallbackthatwillbeexecutedbytheeventlistener
wedefinedinthepreviousstep.Inthismethod,weupdatetheisConnected
propertyofstate.Then,ifthependingSyncclassvariableisdefined,itmeans
we'vegotacachedPOSTrequest,sowe'llsubmitthatrequestandupdatethe
stateaccordingly:
onConnectionChange=(isConnected)=>{
this.setState({isConnected});
if(this.pendingSync){
this.setState({syncStatus:'Syncing'});
this.submitData(this.pendingSync).then(()=>{
this.setState({syncStatus:'SyncComplete'});
});
}
}
5. Next,weneedtoimplementafunctionthatwillactuallymaketheAPIcall
whenthereisanactivenetworkconnection:
submitData(requestBody){
returnfetch('http://jsonplaceholder.typicode.com/posts',{
method:'POST',
body:JSON.stringify(requestBody)
}).then((response)=>{
returnresponse.text();
}).then((responseText)=>{
this.setState({
serverResponse:responseText
});
});
}
6. ThelastthingweneedtodobeforewecanworkonourUIisafunctionfor
handlingtheonPresseventontheSubmitDatabuttonwewillberendering.
Thiswilleitherperformthecallimmediatelyorbesavedinthis.pendingSync
ifthereisnonetworkconnection:
onSubmitPress=()=>{
constrequestBody={
title:'foo',
body:'bar',
userId:1
};
if(this.state.isConnected){
this.submitData(requestBody);
}else{
this.pendingSync=requestBody;
this.setState({syncStatus:'Pending'});
}
}
7. Now,wecanbuildoutourUI,whichwillrendertheSubmitDatabutton
andshowthecurrentconnectionstatus,syncstatus,andmostrecent
responsefromtheAPI:
render(){
const{
isConnected,
syncStatus,
serverResponse
}=this.state;
return(
<Viewstyle={styles.container}>
<TouchableOpacityonPress={this.onSubmitPress}>
<Viewstyle={styles.button}>
<Textstyle={styles.buttonText}>SubmitData</Text>
</View>
</TouchableOpacity>
<Textstyle={styles.status}>
ConnectionStatus:{isConnected?'Connected':
'Disconnected'}
</Text>
<Textstyle={styles.status}>
SyncStatus:{syncStatus}
</Text>
<Textstyle={styles.status}>
ServerResponse:{serverResponse}
</Text>
</View>
);
}
8. Youcandisablethenetworkconnectioninthesimulatorinthesamewayas
describedinstep10ofthepreviousrecipe:
Howitworks...
ThisrecipeleveragestheNetInfomoduletocontrolwhenanAJAXrequest
shouldbemade.
Instep6,wedefinedthemethodthat'sexecutedwhentheSubmitDatabuttonis
pressed.Ifthereisnoconnectivity,wesavetherequestbodyinto
thependingSyncclassvariable.
Instep3,wedefinedthecomponentWillMountlifecyclehook.Here,twoNetInfo
methodcallsretrievethecurrentnetworkconnectionstatusandattachanevent
listenertothechangeevent.
Instep4,wedefinedthefunctionthatwillbeexecutedwheneverthenetwork
connectionhaschanged,whichinformsthestate'sisConnectedBooleanproperty
appropriately.Ifthedeviceisconnected,wealsochecktoseewhetherthereisa
pendingAPIcall,andcompletetherequestifitexists.
Thisrecipecouldalsobeexpandedontosupportaqueuesystemofpending
calls,whichwouldallowmultipleAJAXrequeststobedelayeduntilaninternet
connectionwasre-established.
LogginginwithFacebook
Facebookisthelargestsocialmediaplatforminexistence,withwellover1
billionusersworldwide.Thismeansthatthere'sagoodchancethatyourusers
willhaveaFacebookaccount.Yourappcanregisterandlinkwiththeiraccount,
allowingyoutousetheirFacebookcredentialsasaloginforyourapp.
Dependingontherequestedpermissions,thiswillalsoallowyoutoaccessdata
suchasuserinformation,andpictures,andevengiveyoutheabilitytoaccess
sharedcontent.Youcanreadmoreabouttheavailablepermissionoptionsfrom
theFacebookdocsathttps://developers.facebook.com/docs/facebook-login/permissions#re
ference-public_profile.
Inthisrecipe,wewillcoverabasicmethodforloggingintoFacebookviaanapp
togetasessiontoken.We'llthenusethattokentoaccessthebasic/meendpoint
providedbyFacebook'sGraphAPI,whichwillgiveustheuser'snameand
ID.FormorecomplexinteractionswiththeFacebookGraphAPI,youcanlook
atthedocumentation,whichcanbefoundathttps://developers.facebook.com/docs/gra
ph-api/using-graph-api.
Tokeepthisrecipesimple,wewillbebuildinganExpoappthatuses
theExpo.Facebook.logInWithReadPermissionsAsyncmethodtodotheheavyliftingof
loggingintoFacebook,whichwillalsoallowustobypassmuchofthesetup
that'sotherwisenecessaryforsuchanapp.IfyouwishtointeractwithFacebook
withoutusingExpo,youwilllikelywanttousetheReactNativeFacebook
SDK,whichrequiresalotmoresteps.YoucanfindtheSDKathttps://github.com/
facebook/react-native-fbsdk.
Gettingready
Forthisrecipe,we'llcreateanewappcalledfacebook-login.Youwillneedtohave
anactiveFacebookaccounttotestitsfunctionality.
AFacebookDeveloperaccountisalsonecessaryforthisrecipe.Headovertohtt
ps://developers.facebook.comtosignupifyoudon'thaveone.Onceyouarelogged
in,youcanusethedashboardtocreateanewapp.MakenoteoftheappIDonce
it'sbeencreated,aswe'llneeditfortherecipe.
Howtodoit...
1. Let'sstartbyopeningtheApp.jsfileandaddingourimports:
importReactfrom'react';
import{
StyleSheet,
Text,
View,
TouchableOpacity,
Alert
}from'react-native';
importExpofrom'expo';
2. Next,we'lldeclaretheAppclassandaddthestateobject.Thestatewillkeep
trackofwhethertheuserisloggedinwiththeloggedInBoolean,andwill
savetheretrieveduserdatafromFacebookinanobjectcalled
facebookUserInfo:
exportdefaultclassAppextendsReact.Component{
state={
loggedIn:false,
facebookUserInfo:{}
}
//Definedinlatersteps
}
3. Next,let'sdefinethelogInmethodofourclass.Thiswillbethemethod
that'scalledwhentheLoginbuttonispressed.Thismethoduses
thelogInWithReadPermissionsAsyncExpohelperclassoftheFacebookmethodto
prompttheuserwithaFacebookloginscreen.Replacethefirstparameter,
labeledAPP_IDinthefollowingcode,withyourApp'sID:
logIn=async()=>{
const{type,token}=await
Facebook.logInWithReadPermissionsAsync(APP_ID,{
permissions:['public_profile'],
});
//Definedinnextstep
}
4. InthesecondhalfofthelogInmethod,iftherequestissuccessful,we'll
makeacalltotheFacebookGraphAPIusingthetokenthatwasreceived
fromloggingintorequestthelogged-inuser'sinformation.Oncethe
responseresolves,wesetthestateaccordingly:
logIn=async()=>{
//Definedinstepabove
if(type==='success'){
constresponse=awaitfetch(`https://graph.facebook.com/me?
access_token=${token}`);
constfacebookUserInfo=awaitresponse.json();
this.setState({
facebookUserInfo,
loggedIn:true
});
}
}
5. We'llalsoneedasimplerenderfunction.We'lldisplayaLoginbuttonfor
loggingin,aswellasTextelementsthatwilldisplayuserinformationonce
theloginhascompletedsuccessfully:
render(){
return(
<Viewstyle={styles.container}>
<Textstyle={styles.headerText}>LoginviaFacebook</Text>
<TouchableOpacity
onPress={this.logIn}
style={styles.button}
>
<Textstyle={styles.buttonText}>Login</Text>
</TouchableOpacity>
{this.renderFacebookUserInfo()}
</View>
);
}
6. Asyoucanseeintheprecedingrenderfunction,we'recalling
this.renderFacebookUserInfotorenderuserinformation.Thismethodsimply
checkswhethertheuserinloggedinviathis.state.loggedIn.Iftheyare,we'll
displaytheuser'sinformation.Ifnot,we'llreturnnulltodisplaynothing:
renderFacebookUserInfo=()=>{
returnthis.state.loggedIn?(
<Viewstyle={styles.facebookUserInfo}>
<Textstyle={styles.facebookUserInfoLabel}>Name:</Text>
<Textstyle={styles.facebookUserInfoText}>
{this.state.facebookUserInfo.name}</Text>
<Textstyle={styles.facebookUserInfoLabel}>UserID:</Text>
<Textstyle={styles.facebookUserInfoText}>
{this.state.facebookUserInfo.id}</Text>
</View>
):null;
}
7. Finally,we'lladdstylestocompletethelayout,settingpadding,margins,
color,andfontsizes:
conststyles=StyleSheet.create({
container:{
flex:1,
backgroundColor:'#fff',
alignItems:'center',
justifyContent:'center',
},
button:{
marginTop:30,
padding:10,
backgroundColor:'#3B5998'
},
buttonText:{
color:'#fff',
fontSize:30
},
headerText:{
fontSize:30
},
facebookUserInfo:{
paddingTop:30
},
facebookUserInfoText:{
fontSize:24
},
facebookUserInfoLabel:{
fontSize:20,
marginTop:10,
color:'#474747'
}
});
8. Now,ifweruntheapp,we'llseeourLoginbutton,aloginmodalwhenthe
Loginbuttonispressed,andtheuser'sinformation,whichwillbedisplayed
oncetheuserhassuccessfullyloggedin:
Howitworks...
InteractingwithFacebookinourReactNativeappismademucheasierthanit
otherwisewouldbe,viaExpo'sFacebookhelperlibrary.
Instep5,wecreatedthelogInfunction,whichuses
Facebook.logInWithReadPermissionsAsynctomaketheloginrequesttoFacebook.It
takestwoparameters:anappIDandanoptionsobject.Inourcase,we'reonly
settingthepermissionsoption.Thepermissionsoptiontakesanarrayofstrings
foreachtypeofpermissionrequested,butforourpurpose,weonlyusethemost
basicpermission,'public_profile'.
Instep6,wecompletedthelogInfunction.ItmakesacalltoFacebook'sGraph
APIendpoint,/me,uponsuccessfullogin,usingthetokenprovidedbythedata
that'sreturnedfromlogInWithReadPermissionsAsync.Theuser'sinformationandthe
loginstatusaresavedtostate,whichwilltriggerare-renderanddisplaythe
user'sdataonthescreen.
ThisrecipeintentionallyonlymakesacalltoonesimpleAPIendpoint.You
couldusethereturndatafromthisendpointtopopulateuserdatainyourapp.
Alternatively,youcouldusethesametokenthatwasreceivedfromlogginginto
performanyactionsprovidedbytheGraphAPI.Toseewhatkindofdataisat
yourdisposalviatheAPI,youcanviewthereferencedocsathttps://developers.fa
cebook.com/docs/graph-api/reference.
ImplementingRedux
Inthischapter,we'llgostepbystepthroughtheprocessofaddingReduxtoour
app.We'llcoverthefollowingrecipes:
InstallingReduxandpreparingourproject
Definingactions
Definingreducers
Settingupthestore
CommunicatingwitharemoteAPI
Connectingthestoretotheviews
StoringofflinecontentusingRedux
Showingnetworkconnectivitystatus
Introduction
Atsomepointduringthedevelopmentofmostapplications,we'llneedabetter
waytohandlethestateoftheoverallapp.Thiswilleasesharingdataacross
componentsandprovideamorerobustarchitectureforscalingourappinthe
future.
InordertogetabetterunderstandingofRedux,thestructureofthischapterwill
differfrompreviouschapters,sincewe'llbecreatingoneappthroughallofthese
recipes.Eachrecipeinthischapterwilldependonthelastrecipe.
Wewillbebuildingasimpleappfordisplayinguserposts,andwe'llusea
ListViewcomponenttodisplaythedatareturnedfromtheAPI.We'llbeusingthe
excellentmockdataAPIwe'veusedbeforelocatedathttps://jsonplaceholder.typico
de.com.
InstallingReduxandpreparingour
project
Inthisrecipe,we'llinstallReduxinanemptyapplication,andwe'lldefinethe
basicfolderstructureofourapp.
Gettingstarted
We'llneedanewemptyappforthisrecipe.Let'scallitredux-app.
We'llalsoneedtwodependencies:reduxforhandlingstatemanagementandreact-
reduxforgluingtogetherReduxandReactNative.Youcaninstallthemfromthe
commandlinewithyarn:
yarnaddreduxreact-redux
Oryoucanusenpm:
npminstall--savereduxreact-redux
Howtodoit...
1. Aspartofthisrecipe,we'llbuildoutthefolderstructurethattheappwill
use.Let'saddacomponentsfolderwithanAlbumfolderinsideofittoholdthe
photoalbumcomponent.We'llalsoneedareduxfoldertoholdallofour
Reduxcode.
2. Insidethereduxfolder,let'saddanindex.jsfileforReduxinitialization.We
alsoneedaphotosdirectory,withanactions.jsfileandareducer.jsfile.
3. Fornow,theApp.jsfilewillonlycontainanAlbumcomponent,whichwe'll
definelater:
importReact,{Component}from'react';
import{StyleSheet,SafeAreaView}from'react-native';
importAlbumfrom'./components/Album';
constApp=()=>(
<SafeAreaViewstyle={styles.container}>
<Album/>
</SafeAreaView>
);
conststyles=StyleSheet.create({
container:{
flex:1,
},
});
exportdefaultApp;
Howitworks...
InGettingstarted,weinstalledthereduxandreact-reduxlibraries.Thereact-redux
librarycontainsthenecessarybindingstointegrateReduxwithReact.Reduxis
notexclusivelydesignedtoworkwithReact.YoucanuseReduxwithanyother
JavaScriptlibrariesoutthere.Byusingreact-redux,we'llbeabletoseamlessly
integrateReduxintoourReactNativeapplication.
Instep2,wecreatedthemainfolderswe'lluseforourapp:
Thecomponentsfolderwillcontainourappcomponents.Inthiscase,we're
onlyaddingoneAlbumcomponenttokeepthisrecipesimple.
ThereduxfolderwillcontainalloftheReduxrelatedcode(initialization,
actions,andreducers).
Inamediumtolargeapp,youwillprobablywanttoseparateyourReactNative
componentsfurther.TheReactcommunitystandardistosplittheapp's
componentsintothreeseparatetypes:
Components:Thecommunitycallsthempresentationalcomponents.Insimple
terms,thesearethekindofcomponentsthatarenotawareofanybusiness
logicorReduxactions.Thesecomponentsonlyreceivedataviapropsand
shouldbereusableonanyotherproject.Abuttonorpanelwouldbea
perfectexampleofapresentationalcomponent.
Containers:ThesearecomponentsthatdirectlyreceivedatafromReduxand
areabletocallactions.Inhere,we'lldefinecomponentssuchasaheader
thatdisplaystheloggedinuser.Usually,thesecomponentsinternallyuse
presentationalcomponents.
Pages/Views:Thesearethemainmodulesintheappthatusecontainersand
presentationalcomponents.
FormoreinformationonstructuringyourReduxpoweredcomponents,I
recommendtheexcellentarticle,StructureyourReact-Reduxprojectfor
scalabilityandmaintainability,atthefollowinglink:
https://levelup.gitconnected.com/structure-your-react-redux-project-for-scalability-and-ma
intainability-618ad82e32b7
Wealsocreatedaredux/photosfolder.Inthisfolder,we'llcreatethefollowing:
Theactions.jsfile,whichwillcontainalloftheactionstheappcanperform.
Wewilltalkmoreaboutactionsonthenextrecipe.
Thereducer.jsfile,whichwillcontainallthecodemanagingthedatainthe
Reduxstore.Wewilldigdeeperintothissubjectinlaterrecipes.
Definingactions
Anactionisapayloadofinformationthatsendsdatatothestore.Usingthese
actionsistheonlywaycomponentscanrequestorsenddatatotheReduxstore,
whichservesastheglobalstateobjectfortheentireapp.Anactionisjustaplain
JavaScriptobject.We'llbedefiningfunctionsthatreturntheseactions.A
functionthatreturnsanactioniscalledanactioncreator.
Inthisrecipe,we'llcreatetheactionstoloadtheinitialimagesforthegallery.
Duringthisrecipe,we'llbeaddinghardcodeddata,butlateron,we'llrequestthis
datafromanAPItocreateamorerealisticscenario.
Gettingready
Let'scontinueworkingonthecodefromthepreviousrecipe.Makesureto
followthosestepsinordertohaveReduxinstalledandbuildoutthefolder
structurethatwe'lluseforthisproject.
Howtodoit...
1. We'llneedtodefinetypesforeachtheaction.Openthe
redux/photos/actions.jsfile.Actiontypesaredefinedasconstantsthatcan
laterbereferencedinactionsandreducers,asfollows:
exportconstFETCH_PHOTOS='FETCH_PHOTOS';
2. Nowlet'screateourfirstactioncreator.Everyactionneedsatypeproperty
todefineit,andactionswilloftenhaveapayloadpropertyofdatatopass
alongwiththeaction.Inthisrecipe,we'rehardcodingamockAPIresponse
madeupofanarrayoftwophotoobjects,asfollows:
exportconstfetchPhotos=()=>{
return{
type:FETCH_PHOTOS,
payload:{
"photos":[
{
"albumId":2,
"title":"doloreesseaineossed",
"url":"http://placehold.it/600/f783bd",
"thumbnailUrl":"http://placehold.it/150/d83ea2",
"id":2
},
{
"albumId":2,
"title":"doloreesseaineossed",
"url":"http://placehold.it/600/8e6eef",
"thumbnailUrl":"http://placehold.it/150/bf6d2a",
"id":3
}
]
}
}
}
3. Wewillneedanactioncreatorforeachactionwewanttheapptobeableto
execute,andwewantthisapptobeabletoaddandremoveimages.First,
let'saddtheaddBookmarkactioncreator,asfollows:
exportconstADD_PHOTO='ADD_PHOTO';
exportconstaddPhoto=(photo)=>{
return{
type:ADD_PHOTO,
payload:photo
};
}
4. Likewise,we'llneedanotheractioncreatorforremovingphotos:
exportconstREMOVE_PHOTO='REMOVE_PHOTO';
exportconstremovePhoto=(photo)=>{
return{
type:REMOVE_PHOTO,
payload:photo
};
}
Howitworks...
Instep1,wedefinedtheaction'stypetoindicatewhatitdoes,whichinthiscase
isfetchimages.Weuseaconstantsinceitwillbeusedinmultipleplaces,
includingactioncreators,reducers,andtests.
Instep2,wedeclaredanactioncreator.ActionsaresimpleJavaScriptobjects
thatdefineaneventthathappensinourappthatwillaffectthestateoftheapp.
WeuseactionstointeractwithdatathatlivesintheReduxstore.
There'sonlyonesinglerequirement:eachactionmusthaveatypeproperty.In
addition,anactionwilloftenincludeapayloadpropertythatholdsdatarelevantto
theaction.Inthiscase,weareusinganarrayofphotoobjects.
Anactionisvalidaslongasthetypepropertyisdefined.Ifwewanttosendanythingelse,itis
acommonconventiontousethepayloadpropertyaspopularizedbythefluxpattern.However,
thenamepropertyisn'tinherentlyspecial.Wecouldnamethisparamsordataandthebehavior
wouldremainthesame.
There'smore...
Currently,wehavedefinedtheactioncreators,whicharesimplefunctionsthat
returnactions.Inordertousethem,weneedtousethedispatchmethodprovided
bytheReduxstore.Wewilllearnmoreaboutthestoreinlaterrecipes.
Definingreducers
Atthispoint,wehavecreatedafewactionsforourapp.Asdiscussedearlier,
actionsdefinethatsomethingshouldhappened,butwehaven'tcreatedanything
forputtingtheactionintomotion.That'swherereducerscomein.Reducersare
functionsthatdefinehowanactionshouldaffectthedataintheReduxstore.All
accessingofdatainthestorehappensinareducer.
Reducersreceivetwoparameters:stateandaction.Thestateparameterrepresents
theglobalstateoftheapp,andtheactionparameteristheactionobjectbeing
usedbythereducer.Reducersreturnanewstateparameterreflectingthe
changesthatareassociatedwithagivenactionparameter.Inthisrecipe,we'll
introduceareducerforfetchingthephotosbyusingtheactionswedefinedinthe
previousrecipe.
Gettingready
Thisrecipedependsonthepreviousrecipe,Definingactions.Besuretostart
fromthebeginningofthischaptertoavoidanyproblemsorconfusion.
Howtodoit...
1. Let'sstartbyopeningthephotos/reducer.jsfileandimportingalloftheaction
typeswedefinedinthepreviousrecipe,asfollows:
import{
FETCH_PHOTOS,
ADD_PHOTO,
REMOVE_PHOTO
}from'./actions';
2. We'lldefineaninitialstateobjectforthestateinthisreducer.Ithasaphotos
propertyinitializedtoanemptyarrayforthecurrentlyloadedphotos,as
follows:
constinitialState=()=>return{
photos:[]
};
3. Wecannowdefinethereducerfunction.It'llreceivetwoparameters,the
currentstateandtheactionthathasbeendispatched,asfollows:
exportdefault(state=initialState,action)=>{
//Definedinnextsteps
}
ReactNativecomponentscanalsohaveastateobject,butthatisanentirelyseparatestate
fromthatwhichReduxuses.Inthiscontext,statereferstotheglobalstatestoredinthe
Reduxstore.
4. Stateisimmutable,soinsteadofmanipulatingstate,insidethereducer
function,weneedtoreturnanewstateforthecurrentaction,asfollows:
exportdefault(state=initialState,action)=>{
switch(action.type){
caseFETCH_PHOTOS:
return{
...state,
photos:[...action.payload],
};
//Definedinnextsteps
}
5. Inordertoaddanewbookmarktothearray,allweneedtodoisgetthe
payloadoftheactionandincludeitinthenewarray.Wecanusethespread
operatortospreadthecurrentphotosarrayonstate,then
addaction.payloadtothenewarray,asfollows:
caseADD_PHOTO:
return{
...state,
photos:[...state.photos,action.payload],
};
6. Ifwewanttoremoveanitemfromthearray,wecanusethefiltermethod,
asfollows:
caseREMOVE_PHOTO:
return{
...state,
photos:state.photos.filter(photo=>{
returnphoto.id!==action.payload.id
})
};
7. Thefinalstepistocombineallofthereducersthatwehave.Inalargerapp,
youwilllikelyhavereasontobreakyourreducersintoseparatefiles.Since
we'reonlyusingonereducer,thisstepistechnicallyoptional,butit
illustrateshowmultiplereducerscanbecombinedtogetherwith
Redux'scombineReducershelper.Let'suseitintheredux/index.jsfile,which
we'llalsousetoinitiatetheReduxstoreinthenextrecipe,asfollows:
import{combineReducers}from'redux';
importphotosfrom'./photos/reducers';
constreducers=combineReducers({
photos,
});
Howitworks...
Instep1,weimportedalloftheactiontypesthatwedeclaredintheprevious
recipe.Weusethesetypestodeterminewhatactionshouldbetakenand
howaction.payloadshouldaffecttheReduxstate.
Instep2,wedefinedtheinitialstateofthereducerfunction.Fornow,weonly
needanemptyarrayforourphotos,butwecouldaddotherpropertiestothe
state,suchasBooleanpropertiesofisLoadinganddidErrortotrackloadingand
errorstates.Thesecan,inturn,beusedtoupdatetheUIduringandinresponse
toasyncactions.
Instep3,wedefinedthereducerfunction,whichreceivestwoparameters:the
currentstateandtheactionthatisbeingdispatched.Wesettheinitialstate
toinitialStateifwearenotprovidedwithone.Thisway,wecanensurethatthe
photosarrayexistsatalltimeswithintheapp,whichwillhelpinavoidingerrors
incaseswhereactionsgetdispatchedthatdon'taffecttheReduxstate.
Instep4,wedefinedanactionforfetchingphotos.Rememberthatstateisnever
directlymanipulated.Iftheaction'stypematchesthecase,anewstateobjectis
createdbycombiningthecurrentstate.photosarraywiththeincomingphotos
onaction.payload.
Thereducerfunctionshouldbepure.Thismeansthereshouldn'tbesideeffectson
anyoftheinputvalues.Mutatingthestateortheactionisbadpracticeand
shouldalwaysbeavoided.Amutationcanleadtoinconsistentdataornottrigger
arendercorrectly.Also,inordertopreventsideeffects,weshouldavoid
executinganyAJAXrequestsinsidethereducer.
Instep5,wecreatedtheactionforaddinganewelementtothephotosarray,but
insteadofusingArray.push,wearereturninganewarrayandappendingthe
incomingelementtothelastpositiontoavoidmutatingtheoriginalarrayonthe
state.
Instep6,weaddedanactionforremovingthebookmarkfromthestate.The
easiestwaytodothisisbyusingthefiltermethodsowecanignoretheelement
withtheIDthatwasreceivedontheaction'spayload.
Instep7,weusethecombineReducersfunctiontomergeallofthereducersintoa
singleglobalstateobjectthatwillbesavedinthestore.Thisfunctionwillcall
eachreducerwiththekeyinthestatethatcorrespondstothatreducer;this
functionisexactlythesameasthefollowing:
importphotosReducerfrom'./photos/reducer';
constreducers=function(state,action){
return{
photos:photosReducer(state.photos,action),
};
}
Thephotosreducerhasonlybeencalledonthepartofthestatethatcaresabout
photos.Thiswillhelpyouavoidmanagingallstatedatainasinglereducer.
SettinguptheReduxstore
TheReduxstoreisresponsibleforupdatingtheinformationthatiscalculatedon
thestateinsidereducers.Itisasingleglobalobject,whichcanbeaccessedvia
thestore'sgetStatemethod.
Inthisrecipe,we'lltietogethertheactionsandthereducerwecreatedin
previousrecipes.Wewillusetheexistingactionstoaffectdatathatlivesinthe
store.Wewillalsolearnhowtologchangesonthestatebysubscribingtothe
storechanges.Thisrecipeservesmoreasaproofofconceptofhowactions,
reducers,andthestoreworktogether.We'lldivedeeperintohowReduxismore
commonlyusedwithinappslaterinthischapter.
Howtodoit...
1. Let'sopentheredux/index.jsfileandimportthecreateStorefunction
fromredux,asfollows:
import{combineReducers,createStore}from'redux';
2. Creatingthestoreisextremelysimple;allweneedtodoiscallthefunction
fromstep1andsendthereducersasthefirstparameter,asfollows:
conststore=createStore(reducers);
exportdefaultstore;
3. That'sit!We'vesetupthestore,sonowlet'sdispatchsomeactions.The
nextstepsinthisrecipewillberemovedfromthefinalprojectsincethey're
fortestingoursetup.Let'sstartbyimportingtheactioncreatorswewould
liketodispatch:
import{
loadPhotos,
addPhotos,
removePhotos,
}from'./photos/actions';
4. Beforedispatchinganyaction,let'ssubscribetothestore,whichwillallow
ustolistentoanychangesthatoccurinthestore.Forourcurrentpurposes,
weonlyneedtoconsole.logtheresultofstore.getState(),asfollows:
constunsubscribe=store.subscribe(()=>{
console.log(store.getState());
});
5. Let'sdispatchsomeactionsandseetheresultingstateintheDeveloper
console:
store.dispatch(loadPhotos());
6. Inordertoaddanewbookmark,weneedtodispatchtheaddBookmarkaction
creatorwiththephotosobjectastheparameter:
store.dispatch(addPhoto({
"albumId":2,
"title":"doloreesseaineossed",
"url":`http://placehold.it/600/`,
"thumbnailUrl":`http://placehold.it/150/`
}));
7. Toremoveanitem,wepassalongtheidofthephotowewanttoremoveto
theactioncreator,sincethisiswhatthereducerisusingtofindtheitemthat
shouldbedeleted:
store.dispatch(removePhoto({id:1}));
8. Afterexecutingalloftheseactions,wecanstoplisteningtochangesonthe
storebyrunningtheunsubscribefunctionwecreatedinstep4whenwe
subscribedtothestore,asfollows:
unsubscribe();
9. Weneedtoimporttheredux/index.jsfileintotheApp.jsfile,whichwillrun
allofthecodeinthisrecipesowecanseetherelatedconsole.logmessages
intheDeveloperconsole:
importstorefrom'./redux';
Howitworks...
Instep3,weimportedtheactioncreatorswecreatedintheearlier
recipe,Definingactions.Eventhoughwedon'tyethaveaUI,wecanusethe
Reduxstoreandobservethechangesastheyhappen.Allittakesiscallingan
actioncreatorandthendispatchingtheresultingaction.
Instep5,wecalledthedispatchmethodfromthestoreinstance.dispatchtakesan
action,whichiscreatedbytheloadBookmarksactioncreator.Thereducerwillbe
calledinturn,whichwillsetthenewphotosonthestate.
OncewehaveourUIinplace,we'lldispatchtheactionsinasimilarfashion
fromourcomponents,whichwillupdatethestate,ultimatelytriggeringare-
renderofthecomponent,displayingthenewdata.
CommunicatingwitharemoteAPI
Wearecurrentlyloadingthebookmarksfromhardcodeddataintheaction.Ina
realapp,we'remuchmorelikelytobegettingdatabackfromanAPI.Inthis
recipe,we'lluseaReduxmiddlewaretohelpwiththeprocessoffetchingdata
fromanAPI.
Gettingready
Inthisrecipe,we'llbeusingaxiostomakeallAJAXrequests.Installitwithnpm:
npminstall--saveaxios
Oryoucaninstallitwithyarn:
yarnaddaxios
Forthisrecipe,we'llbeusingtheReduxmiddleware,redux-promise-middleware.
Installthepackagewithnpm:
npminstall--saveredux-promise-middleware
Oryoucaninstallitwithyarn:
yarnaddredux-promise-middleware
Thismiddlewarewillcreateandautomaticallydispatchthreerelatedactionsfor
eachAJAXrequestmadeinourapp:onewhenarequestbegins,onewhena
requestsucceeds,andoneforwhenarequestfails.Usingthismiddleware,we
areabletodefineanactioncreatorthatreturnsanactionobjectwithapromise
forapayload.Inourcase,we'llbecreatingtheasyncaction,FETCH_PHOTOS,whose
payloadisanAPIrequest.Themiddlewarewillcreateanddispatchanactionof
theFETCH_PHOTOS_PENDINGtype.Whentherequestresolves,themiddlewarewill
createanddispatcheitheranactionoftheFETCH_PHOTOS_FULFILLEDtypewiththe
resolveddataasthepayloadiftherequestwassuccessfuloranactionof
theFETCH_PHOTOS_REJECTEDtypewiththeerrorasapayloadiftherequestfailed.
Howtodoit...
1. Let'sstartbyaddingthenewmiddlewaretoourReduxstore.In
theredux/index.jsfile,let'saddtheReduxmethod,applyMiddleware.We'llalso
addthenewmiddlewarewejustinstalled,asfollows:
import{combineReducers,createStore,applyMiddleware}from'redux';
importpromiseMiddlewarefrom'redux-promise-middleware';
2. InthecalltocreateStorethatwedefinedpreviously,wecanpass
inapplyMiddlewareasthesecondparameter.applyMiddlewaretakesone
parameter,whichisthemiddlewarewewanttouse,promiseMiddleware:
conststore=createStore(reducers,applyMiddleware(promiseMiddleware()));
UnlikesomeotherpopularReduxmiddlewaresolutionssuchasredux-thunk,promiseMiddleware
mustbeinvokedwhenitispassedtoapplyMiddleware.Itisafunctionthatreturnsthe
middleware.
3. We'regoingtobemakingrealAPIrequestsinouractionsnow,soweneed
toimportaxiosintoredux/photos/actions.We'llalsoaddtheAPI'sbaseURL.
WeareusingthesamedummydataAPIweusedinpreviouschapters,
hostedathttp://jsonplaceholder.typicode.com,asfollows:
importaxiosfrom'axios';
constAPI_URL='http://jsonplaceholder.typicode.com';
4. Next,we'llupdateouractioncreators.We'llfirstupdatethetypesweneed
forhandlingAJAXrequests,asfollows:
exportconstFETCH_PHOTOS='FETCH_PHOTOS';
exportconstFETCH_PHOTOS_PENDING='FETCH_PHOTOS_PENDING';
exportconstFETCH_PHOTOS_FULFILLED='FETCH_PHOTOS_FULFILLED';
exportconstFETCH_PHOTOS_REJECTED='FETCH_PHOTOS_REJECTED';
5. Insteadofreturningdummydataaspayloadforthisaction,we'llreturnaGET
request.SincethisisaPromise,itwilltriggerournewmiddleware.Also,
noticehowtheaction'stypeisFETCH_PHOTOS.Thiswillcausethemiddleware
toautomaticallycreateFETCH_PHOTOS_PENDING,FETCH_PHOTOS_FULFILLEDwithapayload
ofresolveddatawhensuccessful,andFETCH_PHOTOS_REJECTEDwithapayloadof
theerrorthatoccurred,asfollows:
exportconstfetchPhotos=()=>{
return{
type:FETCH_PHOTOS,
payload:axios.get(`${API_URL}/photos?_page=1&_limit=20`)
}
}
6. JustliketheFETCH_PHOTOSaction,we'llbemakinguseofthesamemiddleware
providedtypesfortheADD_PHOTOaction,asfollows:
exportconstADD_PHOTO='ADD_PHOTO';
exportconstADD_PHOTO_PENDING='ADD_PHOTO_PENDING';
exportconstADD_PHOTO_FULFILLED='ADD_PHOTO_FULFILLED';
exportconstADD_PHOTO_REJECTED='ADD_PHOTO_REJECTED';
7. Theactioncreatoritselfwillnolongerjustreturnthepassedinphotoasthe
payload,butinsteadwillpassaPOSTrequestpromiseforaddingtheimagevia
theAPI,asfollows:
exportconstaddPhoto=(photo)=>{
return{
type:ADD_PHOTO,
payload:axios.post(`${API_URL}/photos`,photo)
};
}
8. WecanfollowthesamepatterntoconverttheREMOVE_PHOTOactionintoan
AJAXrequestthatusestheAPItodeleteaphoto.Liketheothertwoaction
creatorsforADD_PHOTOandFETCH_PHOTOS,we'lldefinetheactiontypesforeach
action,thenreturnthedeleteaxiosrequestastheaction'spayload.Sincewe'll
needphotoIdinthereducerwhenweremovetheimageobjectfromthe
Reduxstore,wealsopassthatalongasanobjectonthe
action'smetaproperty,asfollows:
exportconstREMOVE_PHOTO='REMOVE_PHOTO';
exportconstREMOVE_PHOTO_PENDING='REMOVE_PHOTO_PENDING';
exportconstREMOVE_PHOTO_FULFILLED='REMOVE_PHOTO_FULFILLED';
exportconstREMOVE_PHOTO_REJECTED='REMOVE_PHOTO_REJECTED';
exportconstremovePhoto=(photoId)=>{
console.log(`${API_URL}/photos/${photoId}`);
return{
type:REMOVE_PHOTO,
payload:axios.delete(`${API_URL}/photos/${photoId}`),
meta:{photoId}
};
}
9. Wealsoneedtorevisitourreducerstoadjusttheexpectedpayload.
Inredux/reducers.js,we'llstartbyimportingalloftheactiontypeswe'llbe
using,andwe'llupdateinitialState.Forreasonsthatwillbeapparentinthe
nextrecipe,let'srenamethearrayofphotosonthestateobject
toloadedPhotos,asfollows:
import{
FETCH_PHOTOS_FULFILLED,
ADD_PHOTO_FULFILLED,
REMOVE_PHOTO_FULFILLED,
}from'./actions';
constinitialState={
loadedPhotos:[]
};
10. Inthereduceritself,updateeachcasetotaketheFULFILLEDvariationofthe
baseaction:FETCH_PHOTOSbecomesFETCH_PHOTOS_FULFILLED,ADD_PHOTOS
becomesADD_PHOTOS_FULFILLED,andREMOVE_PHOTOSbecomesREMOVE_PHOTOS_FULFILLED.
We'llalsoupdateallofthereferencestothephotosarrayofstatefromphotos
toloadedPhotos.Whenusingaxios,allresponseobjectswillcontainadata
parameterthatholdstheactualdatareceivedfromtheAPI,whichmeans
we'llalsoneedtoupdateallreferencesofaction.payloadtoaction.payload.data.
AndintheREMOVE_PHOTO_FULFILLEDreducer,wecannolongerfindphotoId
ataction.payload.id,whichiswhywepassedphotoIdontheaction'smeta
propertyinstep8,thereforeaction.payload.idbecomesaction.meta.photoId,as
follows:
exportdefault(state=initialState,action)=>{
switch(action.type){
caseFETCH_PHOTOS_FULFILLED:
return{
...state,
loadedPhotos:[...action.payload.data],
};
caseADD_PHOTO_FULFILLED:
return{
...state,
loadedPhotos:[action.payload.data,...state.loadedPhotos],
};
caseREMOVE_PHOTO_FULFILLED:
return{
...state,
loadedPhotos:state.loadedPhotos.filter(photo=>{
returnphoto.id!==action.meta.photoId
})
};
default:
returnstate;
}
}
Howitworks...
Instep2,weappliedthemiddlewarethatwasinstalledintheGettingstarted
section.Asmentionedbefore,thismiddlewarewillallowustomakejustone
actioncreatorforAJAXactionsthatautomaticallycreatesindividualaction
creatorsforthePENDING,FULFILLED,andREJECTEDrequeststates.
Instep5,wedefinedthefetchPhotosactioncreator.You'llrecallfromtheprevious
recipesthatactionsareplainJavaScriptobjects.SincewedefinedaPromiseon
theaction'spayloadproperty,redux-promise-middlewarewillinterceptthisactionand
automaticallycreatethethreeassociatedactionsforthethreepossiblerequest
states.
Instep7andstep8,wedefinedtheaddPhotoactioncreatorandtheremovePhoto
actioncreatorwhich,justlikefetchPhotos,haveanAJAXrequestastheaction
payload.
Byutilizingthismiddleware,weareabletoavoidrepeatingthesameboilerplate
overandoverformakingdifferentAJAXrequests.
Inthisrecipe,weonlyhandledthesuccessconditionsoftheAJAXrequests
madeintheapp.Itwouldbewiseinarealapptoalsohandletheerrorstates
representedwithactionstypesendingin_REJECTED.Thiswillbeagreatplaceto
handleanerrorbysavingittotheReduxstore,sothattheviewcandisplayerror
informationwhenitoccurs.
Connectingthestoretotheview
Sofar,wehavesetupthestate,wehaveincludedmiddleware,andwe've
definedactions,actioncreators,andreducersforinteractingwitharemoteAPI.
However,wearenotabletoshowanyofthisdataonthescreen.Inthisrecipe,
we'llenableourcomponenttoaccessthestorethatwehavecreated.
Gettingready
Thisrecipedependsonallofthepreviousones,somakesuretofolloweach
recipeprecedingthisone.
Inthefirstrecipeofthischapter,weinstalledthereact-reduxlibraryalongwith
ourotherdependencies.Inthisrecipe,wearefinallygoingtomakeuseofit.
We'llalsobeusingathird-partylibraryforgeneratingrandomcolorhexes,
whichwe'llusetorequestcoloredimagesfromtheplaceholderimageserviceat
https://placehold.it/.Beforewebegin,installrandomcolorwithnpm:
npminstall--saverandomcolor
Oryoucaninstallitwithyarn:
yarnaddrandomcolor
Howtodoit...
1. Let'sstartbywiringtheReduxstoretotheReactNativeappinApp.js.We'll
startwiththeimports,importingProviderfromreact-reduxandthestorewe
createdearlier.We'llalsoimporttheAlbumcomponentwe'llbedefining
shortly,asfollows:
importReact,{Component}from'react';
import{StyleSheet,SafeAreaView}from'react-native';
import{Provider}from'react-redux';
importstorefrom'./redux';
importAlbumfrom'./components/Album';
2. It'sthejobofeprop,wherewe'llpassinourReduxstore.Theappandth
ProvidertoconnectourReduxstoretotheReactNativeappsothattheapp's
componentscancommunicatewiththestore.Providershouldbeusedto
wraptheentireapp,andsincethisapplivesintheAlbumcomponent,we'll
wraptheAlbumcomponentwiththeProvidercomponent.Providertakesastore
prop,wherewe'llpassinourReduxstore.Theappandthestorearewired:
constApp=()=>(
<Providerstore={store}>
<Album/>
</Provider>
);
exportdefaultApp;
3. Let'sturntotheAlbumcomponent.Thecomponentwilllive
atcomponents/Album/index.js.We'llstartwiththeimports.We'llimportthe
randomcolorpackageforgeneratingrandomcolorhexes,asmentionedin
theGettingstartedsection.We'llalsoimportconnectfromreact-redux,andthe
actioncreatorswedefinedinpreviousrecipes.connectwillwireourappto
theReduxstore,andwecanthenusetheactioncreatorstoaffectthestore's
state,asfollows:
importReact,{Component}from'react';
import{
StyleSheet,
Text,
View,
SafeAreaView,
ScrollView,
Image,
TouchableOpacity
}from'react-native';
importrandomColorfrom'randomcolor';
import{connect}from'react-redux';
import{
fetchPhotos,
addPhoto,
removePhoto
}from'../../redux/photos/actions';
4. Let'screatetheAlbumclass,however,insteadofdirectlyexportingAlbumas
thedefaultexport,we'lluseconnecttowireAlbumtothestore.Notethatconnect
iscalledwithtwosetsofparenthesesandthatthecomponentispassedinto
thesecondset,asfollows:
classAlbumextendsComponent{
}
exportdefaultconnect()(Album);
5. Thefirstsetofparenthesesinacalltoconnecttakestwofunction
parameters:mapStateToPropsandmapDispatchToProps.We'lldefinemapStateToProps
first,whichtakesstateasaparameter.ThisstateisourglobalReduxstate
objectcontainingallofourdata.Thefunctionreturnsanobjectofthe
piecesofstatethatwewanttouseinourcomponent.Inourcase,wejust
needtheloadedPhotospropertyfromthephotosreducer.Bysettingthisvalue
tophotosinthereturnobject,wecanexpectthis.props.photostobethevalue
storedinstate.photos.loadedPhotos.Anditwillchangeautomaticallywhenthe
Reduxstoreisupdated:
classAlbumextendsComponent{
}
constmapStateToProps=(state)=>{
return{
photos:state.photos.loadedPhotos
}
}
exportdefaultconnect(mapStateToProps)(Album);
6. Similarly,themapDispatchToPropsfunctionwillmapouractioncreatorstothe
component'spropsaswell.ThefunctionreceivestheRedux
method,dispatch,whichisusedtoexecuteanactioncreator.We'llmapthe
executionofeachactioncreatortoakeyofthesamename,so
thatthis.props.fetchPhotos()willexecutedispatch(fetchPhotos()),andsoon,as
follows:
classAlbumextendsComponent{
}
constmapStateToProps=(state)=>{
return{
photos:state.photos.loadedPhotos
}
}
constmapDispatchToProps=(dispatch)=>{
return{
fetchPhotos:()=>dispatch(fetchPhotos()),
addPhoto:(photo)=>dispatch(addPhoto(photo)),
removePhoto:(id)=>dispatch(removePhoto(id))
}
}
exportdefaultconnect(mapStateToProps,mapDispatchToProps)(Album);
7. Nowthatwe'vegotourReduxstorewiredtoourcomponent,let'screatethe
componentitself.WecanmakeuseofthecomponentDidMountlifecyclehookto
fetchourphotos,asfollows:
classAlbumextendsComponent{
componentDidMount(){
this.props.fetchPhotos();
}
//Definedonlatersteps
}
8. Wewillalsoneedamethodforaddingphotos.Here,we'llusetherandomcolor
package(importedasrandomColorbyconvention)tocreateanimagewiththe
placehold.itservice.Thegeneratedcolorstringcomesbackwithahash#
prefixingthehexvalue,whichtherequesttotheimageservicedoesn't
want,sowecansimplyremoveitwithareplacecall.Toaddthephoto,we
justcalltheaddPhotofunctionmappedtoprops,passinginthenewphoto
object,asfollows:
addPhoto=()=>{
constphoto={
"albumId":2,
"title":"doloreesseaineossed",
"url":`http://placehold.it/600/${randomColor().replace('#',
'')}`,
"thumbnailUrl":
`http://placehold.it/150/${randomColor().replace('#','')}`
};
this.props.addPhoto(photo);
}
9. WewillalsoneedaremovePhotofunction.Allthisfunctionneedstodoiscall
theremovePhotofunctionthathasbeenmappedtoprops,passingintheIDof
thephototoberemoved,asfollows:
removePhoto=(id)=>{
this.props.removePhoto(id);
}
10. ThetemplatefortheappwillneedaTouchableOpacitybuttonforadding
photos,aScrollViewforholdingalloftheimagesinascrollablelist,andall
ofourimages.EachImagecomponentwillalsobewrappedin
aTouchableOpacitycomponentforcallingtheremovePhotomethodwhenan
imageispressed,asfollows:
render(){
return(
<SafeAreaViewstyle={styles.container}>
<Textstyle={styles.toolbar}>Album</Text>
<ScrollView>
<Viewstyle={styles.imageContainer}>
<TouchableOpacitystyle={styles.button}onPress=
{this.addPhoto}>
<Textstyle={styles.buttonText}>AddPhoto</Text>
</TouchableOpacity>
{this.props.photos?this.props.photos.map((photo)=>{
return(
<TouchableOpacityonPress={()=>
this.removePhoto(photo.id)}key={Math.random()}>
<Imagestyle={styles.image}
source={{uri:photo.url}}
/>
</TouchableOpacity>
);
}):null}
</View>
</ScrollView>
</SafeAreaView>
);
}
11. Finally,we'lladdstylessothattheapphasalayout,asfollows.There's
nothingherewehaven'tcoveredmanytimesbefore:
conststyles=StyleSheet.create({
container:{
backgroundColor:'#ecf0f1',
flex:1,
},
toolbar:{
backgroundColor:'#3498db',
color:'#fff',
fontSize:20,
textAlign:'center',
padding:20,
},
imageContainer:{
flex:1,
flexDirection:'column',
justifyContent:'center',
alignItems:'center',
},
image:{
height:300,
width:300
},
button:{
margin:10,
padding:20,
backgroundColor:'#3498db'
},
buttonText:{
fontSize:18,
color:'#fff'
}
});
12. Theappiscomplete!ClickingontheAddPhotobuttonwilladdanew
phototothebeginningofthelistofimages,andpressinganimagewill
removeit.Note,sinceweareusingadummydataAPI,thePOSTandDELETE
requestswillreturnproperresponsesforthegivenaction.However,nodata
isactuallyaddedordeletedtothedatabase.Thismeansthattheimagelist
willresetiftheappisrefreshed,andthatyoucanexpecterrorsifyou
attempttodeleteanyphotosyou'vejustaddedwiththeAddPhotobutton.
FeelfreetoconnectthisapptoarealAPIanddatabasetoseetheexpected
results:
Howitworks...
Instep4,weusedtheconnectmethodprovidedbyreact-reduxtoempowertheAlbum
componentwithaconnectiontotheReduxstorewe'vebeenworkingonthis
entirechapter.Thecalltoconnectreturnsafunctionthatisimmediatelyexecuted
viathesecondsetofparentheses.BypassingtheAlbumcomponentintothis
returningfunction,connectgluesthecomponentandthestoretogether.
Instep5,wedefinedthemapStateToPropsfunction.Thefirstparameterinthis
functionisstatefromtheReduxstore,whichisinjectedintothefunction
byconnect.WhateverkeysaredefinedintheobjectreturnedfrommapStateToProps
willbepropertiesonthecomponent'sprops.Thevalueofthesepropswillbe
subscribedtostateintheReduxstore,sothatanychangeaffectingthesepieces
ofstatewillbeautomaticallyupdatedwithinthecomponent.
WhilemapStateToPropswillmapstateintheReduxstoretothecomponent
props,mapDispatchToPropswillmaptheactioncreatorstothecomponentprops.In
step6,wedefinedthisfunction.IthasthespecialReduxmethod,dispatch,
injectedintoitforcallingactioncreatorsthatliveinthestore.mapDispatchToProps
returnsanobject,mappingthedispatchcallsforactionstothecomponentsprops
atthespecifiedkeys.
Instep7,wecreatedthecomponentDidMountmethod.Allthecomponentneedstodo
togetthephotositneedswhilemountingistocalltheactioncreatormapped
tothis.props.fetchPhotos.That'sall!ThefetchPhotosactioncreatorwillbe
dispatched.ThefetchPhotoactionreturnedfromtheactioncreatorwillbe
processedbytheredux-promise-middlewareweappliedinapreviousrecipesince
thepayloadpropertyofthisactionhasaPromisestoredonitintheformofan
axiosAJAXrequest.Themiddlewarewillintercepttheaction,processthe
request,andsendanewactiontothereducerswiththeresolveddataon
thepayloadproperty.Ifitwasasuccessfulrequest,theactionwith
theFETCH_PHOTOS_FULFILLEDtypewillbedispatchedwiththeresolveddata,andif
not,theFETCH_PHOTOS_REJECTEDactionwillbedispatchedwiththeerroraspayload.On
success,thecaseinthereducerforhandlingFETCH_PHOTOS_FULFILLEDwill
execute,loadedPhotoswillbeupdatedinthestore,andinturn,this.props.photoswill
alsobeupdated.Updatingthecomponentpropswilltriggerare-render,andthe
newdatawillbedisplayedonthescreen.
Instep8andstep9,wefollowedthesamepatterntodefineaddPhoto
andremovePhoto,whichcalltheactioncreatorsofthesamename.Theaction
producedbytheactioncreatorsarehandledbythemiddleware,theproper
reducerhandlestheresultingaction,andifthestateintheReduxstorechanges,
allsubscribedpropswillbeautomaticallyupdated!
StoringofflinecontentusingRedux
Reduxisanexcellenttoolforkeepingtrackofanapp'sstatewhileitit'srunning.
ButwhatifwehavedatathatweneedtostorewithoutusinganAPI?For
instance,wecouldsavethestateofacomponentsothatwhenauserclosesand
reopenstheapp,thepreviousstateofthatcomponentcanberestored,allowing
ustopersistapieceofanapp'spersistentacrosssessions.Reduxdatapersistence
couldalsobeusefulforcachinginformationtoavoidcallingtheAPImorethan
necessary.YoucanrefertotheMaskingtheapplicationuponnetwork
connectionlossrecipeinChapter8,WorkingwithApplicationLogicandData,for
moreinformationonhowtodetectandhandlenetworkconnectivitystatus.
Gettingready
Thisrecipedependsonthepreviousones,somakesuretofollowalongwithall
ofthepreviousrecipes.Inthisrecipe,we'llbeusingtheredux-persistpackageto
persistthedatainourapp'sReduxstore.Installitwithnpm:
npminstall--saveredux--persist
Oryoucaninstallitwithyarn:
yarnaddredux--persist
Howtodoit...
1. Let'sstartbyaddingthedependencieswe'llneedinredux/index.js.
Thestoragemethodwe'reimportingfromredux-persistherewilluseReact
Native'sAsyncStoragemethodtostoreReduxdatabetweensessions,as
follows:
import{persistStore,persistReducer}from'redux-persist'
importstoragefrom'redux-persist/lib/storage';
2. We'llbeusingasimpleconfigobjectforconfiguringourredux-persist
instance.configrequiresakeypropertyforthekeyusedtostorethedatawith
AsyncStoreandastoragepropertythattakesthestorageinstance,asfollows:
constpersistConfig={
key:'root',
storage
}
3. We'llusethepersistReducermethodweimportedinstep1.Thismethodtakes
theconfigobjectwecreatedinstep2asthefirstargumentandourreducers
asthesecond:
constreducers=combineReducers({
photos,
});
constpersistedReducer=persistReducer(persistConfig,reducers);
4. Nowlet'supdateourstoretousethenewpersistedReducermethod.Alsonote
howwenolongerexportstoreasthedefaultexport,sincewe'llneedtwo
exportsfromthisfile:
exportconststore=createStore(persistedReducer,applyMiddleware(promiseMiddleware()));
5. Thesecondexportweneedfromthisfileispersistor.persistorwillworkto
persisttheReduxstorebetweensessions.Wecancreatepersistorbycalling
thepersistStoremethodandpassinginstore,asfollows:
exportconstpersistor=persistStore(store);
6. Nowthatwe'vegotbothstoreandpersistorasexportsfromredux/index.js,
we'rereadytoapplytheminApp.js.We'llstartbyimportingthem,andwe'll
importthePersistGatecomponentfromredux-persist.PersistGatewillensure
thatourcachedReduxstoreisloadedbeforeanycomponentsareloaded:
import{PersistGate}from'redux-persist/integration/react'
import{store,persistor}from'./redux';
7. Let'supdatetheAppcomponenttousePersistGate.Thecomponenttakestwo
props:theimportedpersistorpropandaloadingprop.We'llbepassingnullto
theloadingprop,butifwehadaloadingindicatorcomponent,wecouldpass
thisin,andPersistGatewoulddisplaythisloadingindicatorasdatais
restored,asfollows:
constApp=()=>(
<Providerstore={store}>
<PersistGateloading={null}persistor={persistor}>
<Album/>
</PersistGate>
</Provider>
);
8. InordertotestthepersistenceofourReduxstore,let'sadjust
thecomponentDidMountmethodintheAlbumcomponent.We'lldelaythecall
tofetchPhotosfortwoseconds,sothatwecanseethesaveddatabeforeitis
fetchedagainfromtheAPI,asfollows:
componentDidMount(){
setTimeout(()=>{
this.props.fetchPhotos();
},2000);
}
Dependingonwhatkindofdatayou'repersisting,thiskindoffunctionality
couldbeappliedtoanumberofsituations,includingpersistinguserdataandapp
state,evenaftertheapp'sbeenclosed.Itcanalsobeusedtoimprovetheoffline
experienceofanapp,cachingAPIrequestsiftheycan'tbemaderightawayand
providinguserswithdatafilledviews.
Howitworks...
Instep2,wecreatedtheconfigobjectforconfiguringredux-persist.Theobjectis
onlyrequiredtohavethekeyandstoreproperties,butalsosupportsquiteafew
others.Youcanseealloftheoptionsthisconfigtakesviathetypedefinition
hostedhere:https://github.com/rt2zz/redux-persist/blob/master/src/types.js#L13-L27.
Instep7,weusedthePersistGatecomponent,whichishowthedocumentation
recommendsdelayingrenderinguntilrestoringpersisteddataiscomplete.Ifwe
havealoadingindicatorcomponent,wecanpassittotheloadingpropforbeing
displayedwhiledataisrestored.
AppWorkflowandThird-Party
Plugins
Thischapterworksabitdifferently,sowewillfirstlookintoitbeforewego
aheadandcoverthefollowingrecipes:
ReactNativedevelopmenttools
Planningyourappandchoosingyourworkflow
UsingNativeBaseforcross-platformUIcomponents
Usingglamorous-nativeforstylingUIcomponents
Usingreact-native-spinkitforaddinganimatedloadingindicators
Usingreact-native-side-menuforaddingsidenavigationmenus
Usingreact-native-modalboxforaddingmodals
Howthischapterworks
Inthischapter,we'llbetakingacloserlookathoweachmethodofbootstrapping
anewReactNativeworks,andhowwecanintegratethird-partypackagesthat
mayormaynotbeExpofriendly.Inpreviouschapters,thefocushasbeen
entirelyonbuildingfunctionalpiecesofaReactNativeapp.Inthischapter,
manyoftheserecipeswillthereforealsoserveasecondarypurposeof
illustratinghowdifferentpackagescanbeimplementedusingdifferent
workflows.
Ineachoftherecipesinthischapter,wewillbeginwithapureReactNative
projectinitializedwiththeReactNativeCLIcommand,whichisdoneas
follows:
react-nativeinit
Otherwise,wemaydosowithanappcreatedviaExpo.
WhencreatinganewReactNativeapp,you'llneedtochoosetherighttooling
forinitializingyourapp.Generallyspeaking,thetoolsyouuseforbootstrapping
anddevelopingyourReactNativeappwilleitherfocusonstreamliningthe
developmentprocessandpurposefullyobfuscatingnativecodefromyouforthe
sakeofeaseandmentaloverhead,orkeepyourdevelopmentprocessflexibleby
providingaccesstoallnativecodeandallowingtheuseofmorethird-party
plugins.
Therearetwomethodsforinitializinganddevelopingyourapp:Expoandthe
ReactNativeCLI.Untilrecently,therewasadistinctthirdmethod,usingCreate
ReactNativeApp(CRNA).CRNAhassincebeenmergedwiththeExpo
project,andonlycontinuestoexistasaseparateentitytoprovidebackwards
compatibility.
Expofallsintothefirstcategoryoftools,providingamorerobustand
developer-friendlydevelopmentworkflowatthecostofsomeflexibility.Apps
bootstrappedwithExpoalsohaveaccesstoamultitudeofusefulfeatures
providedbytheExpoSDK,suchasBarcodeScanner,MapView,ImagePicker,andso
manymore.
InitializeanappwiththeReactNativeCLI,viathefollowingcommand:
react-nativeinit
Thisprovidesflexibilityatthecostofeaseofdevelopment.AReactNativeapp
iscreatedwiththiscommand:
react-nativeinit
ItissaidtobeapureReactNativeapp,sincenoneofthenativecodeishidden
awayfromthedeveloper.
Asaruleofthumb,thedocumentationforathird-partypackagestatesthatyou
needtorunthefollowingcommand:
react-nativelink
Asitispartofthesetupprocess,thispackagecanonlybeusedwithapureReact
Nativeapp.
SowhatdoyoudowhenyouarehalfwaythroughbuildinganappwithExpo,
onlytofindoutthatapackageintegraltoyourapp'srequirementsisnot
supportedbyanExpodevelopmentworkflow?Luckily,Expohasamethodfor
turninganExpoprojectintoapureReactNativeapp,justasifithadbeen
createdwiththefollowingcommand:
react-nativeinit
Whenaprojectisejected,alloftheNativecodeisunpackedintoiosandandroid
folders,andtheApp.jsfileissplitintoApp.jsandindex.js,exposingthecodethat
mountstherootReactNativecomponent.
ButwhatifyourExpoappdependsonfeaturesprovidedbytheExpoSDK?
Afterall,muchofthevalueofdevelopingwithExpocomesfromtheexcellent
featurestheSDKprovides,includingAuthSession,Permissions,WebBrowser,andso
muchmore.
That’swhereExpoKitcomesintoplay.Whenyouchoosetoejectfromaproject,
you’regiventheoptionofincludingExpoKitaspartoftheejectedproject.
IncludingExpoKitwillensurethatalloftheExpodependenciesbeingusedin
yourappwillcontinuetowork,andalsogiveyoutheabilitytocontinueusing
allthefeaturesoftheExpoSDK,evenaftertheapphasbeenejected.
Foradeeperunderstandingoftheejectprocesses,youcanreadtheExpo
documentationathttps://docs.expo.io/versions/latest/expokit/eject.
ReactNativedevelopmenttools
Aswithanydevelopmenttools,thereisgoingtobeatrade-offbetween
flexibilityandeaseofuse.IencourageyoustartbyusingExpoforyourReact
Nativedevelopmentworkflow,unlessyou’resureyou’llneedaccesstothe
nativecode.
Expo
Thiswastakenfromtheexpo.iosite:
"ExpoisafreeandopensourcetoolchainbuiltaroundReactNativetohelpyoubuildnativeiOSand
AndroidprojectsusingJavaScriptandReact."
Expoisbecominganecosystemofitsown,andismadeupoffive
interconnectedtools:
ExpoCLI:Thecommand-lineinterfaceforExpo.We'llbeusingtheExpo
CLItocreate,build,andserveapps.Alistofallthecommandssupported
bytheCLIcanbefoundintheofficialdocumentationatthefollowinglink:
https://docs.expo.io/versions/latest/workflow/expo-cli
Expodevelopertools:Thisisabrowser-basedtoolthatautomaticallyruns
wheneveranExpoappisstartedfromtheTerminalviatheexpostart
command.Itprovidesactivelogsforyourin-developmentapp,andquick
accesstorunningtheapplocallyandsharingtheappwithother
developers.
ExpoClient:AnappforAndroidandiOS.Thisappallowsyoutorunyour
ReactNativeprojectwithintheExpoapponthedevice,withouttheneed
forinstallingit.Thisallowsdeveloperstohotreloadonarealdevice,or
sharedevelopmentcodewithanyoneelsewithouttheneedforinstallingit.
ExpoSnack:Hostedathttps://snack.expo.io,thiswebappallowsyouto
workonaReactNativeappinthebrowser,withalivepreviewofthecode
you’reworkingon.Ifyou'veeverusedCodePenorJSFiddle,Snackisthe
sameconceptappliedtoReactNativeapplications.
ExpoSDK:ThisistheSDKthathousesawonderfulcollectionof
JavaScriptAPIsthatprovideNativefunctionalitynotfoundinthebase
ReactNativepackage,includingworkingwiththedevice'saccelerometer,
camera,notifications,geolocation,andmanyothers.ThisSDKcomes
bakedinwitheverynewprojectcreatedwithExpo.
ThesetoolstogethermakeuptheExpoworkflow.WiththeExpoCLI,youcan
createandbuildnewapplicationswithExpoSDKsupportbakedin.The
XDE/CLIalsoprovidesasimplewaytoserveyourin-developmentappby
automaticallypushingyourcodetoAmazonS3andgeneratingaURLforthe
project.Fromthere,theCLIgeneratesaQRcodelinkedtothehostedcode.
OpentheExpoClientapponyouriPhoneorAndroiddevice,scantheQRcode,
andBOOMthere’syourapp,equippedwithlive/hotreload!Andsincetheappis
hostedonAmazonS3,youcanevensharethein-developmentappwithother
developersinrealtime.
ReactNativeCLI
TheoriginalbootstrappingmethodforcreatinganewReactNativeappusing
thecommandisasfollows:
react-nativeinit
ThisisprovidedbytheReactNativeCLI.You'lllikelyonlybeusingthismethod
ofbootstrappinganewappifyou'resureyou'llneedaccesstothenativelayerof
theapp.
IntheReactNativecommunity,anappcreatedwiththismethodissaidtobea
pureReactNativeapp,sinceallofthedevelopmentandNativecodefilesare
exposedtothedeveloper.Whilethisprovidesthemostfreedom,italsoforces
thedevelopertomaintainthenativecode.Ifyou’reaJavaScriptdeveloperthat’s
jumpedontotheReactNativebandwagonbecauseyouintendonwritingnative
applicationssolelywithJavaScript,havingtomaintainthenativecodeinaReact
Nativeprojectisprobablythebiggestdisadvantageofthismethod.
Ontheotherhand,you'llhaveaccesstothird-partypluginswhenworkingonan
appthat'sbeenbootstrappedwiththefollowingcommand:
react-nativeinit
Getdirectaccesstothenativeportionofthecodebase.You'llalsobeableto
sidestepafewofthelimitationsinExpocurrently,particularlytheinabilityto
usebackgroundaudioorbackgroundGPSservices.
CocoaPods
Onceyoubeginworkingwithappsthathavecomponentsthatusenativecode,
you'regoingtobeusingCocoaPodsinyourdevelopmentaswell.CocoaPodsis
adependencymanagerforSwiftandObjective-CCocoaprojects.Itworks
nearlythesameasnpm,butmanagesopensourcedependenciesfornativeiOS
codeinsteadofJavaScriptcode.
Wewon'tbeusingCocoaPodsmuchinthisbook,butReactNativemakesuseof
CocoaPodsforsomeofitsiOSintegration,sohavingabasicunderstandingof
themanagercanbehelpful.Justasthepackage.jsonfilehousesallofthepackages
foraJavaScriptprojectmanagedwithnpm,CocoaPodsusesaPodfileforlistinga
project'siOSdependencies.Likewise,thesedependenciescanbeinstalledusing
thecommand:
podinstall
RubyisrequiredforCocoaPodstorun.Runthecommandatthecommandline
toverifyRubyisalreadyinstalled:
ruby-v
Ifnot,itcanbeinstalledwithHomebrewwiththecommand:
brewinstallruby
OnceRubyhasbeeninstalled,CocoaPodscanbeinstalledviathecommand:
sudogeminstallcocoapods
Ifyouencounteranyissueswhileinstalling,youcanreadtheofficialCocoaPods
GettingStartedguideathttps://guides.cocoapods.org/using/getting-started.html.
Planningyourappandchoosingyour
workflow
Whentryingtochoosewhichdevelopmentworkflowbestfitsyourapp'sneeds,
hereareafewthingsyoushouldconsider:
WillIneedaccesstothenativeportionofthecodebase?
WillIneedanythird-partypackagesinmyappthatarenotsupportedby
Expo?
Willmyappneedtoplayaudiowhileitisnotintheforeground?
Willmyappneedlocationserviceswhileitisnotintheforeground?
WillIneedpushnotificationsupport?
AmIcomfortableworking,atleastnominally,inXcodeandAndroid
Studio?
Inmyexperience,Expousuallyservesasthebeststartingplace.Itprovidesalot
ofbenefitstothedevelopmentprocess,andgivesyouanescapehatchinthe
ejectprocessifyourappgrowsbeyondtheoriginalrequirements.Iwould
recommendonlystartingdevelopmentwiththeReactNativeCLIifyou'resure
yourappneedssomethingthatcannotbeprovidedbyanExpoapp,orifyou're
sureyouwillneedtoworkontheNativecode.
IalsorecommendbrowsingtheNativeDirectoryhostedathttp://native.directory.
Thissitehasaverylargecatalogofthethird-partypackagesavailableforReact
Nativedevelopment.Eachpackagelistedonthesitehasanestimatedstability,
popularity,andlinkstodocumentation.ArguablythebestfeatureoftheNative
Directory,however,istheabilitytofilterpackagesbywhatkindof
device/developmenttheysupport,includingiOS,Android,Expo,andweb.This
willhelpyounarrowdownyourpackagechoicesandbetterindicatewhich
workflowshouldbeadoptedforagivenapp.
Howtodoit...
We'llbeginwiththeReactNativeCLIsetupofourapp,whichwillcreateanew
pureReactNativeapp,givingusaccesstoalloftheNativecode,butalso
requiringthatXcodeandAndroidStudioareinstalled.
YoumayrecallfromChapter1,SettingUpYourEnvironment,thatsomeofthesestepshave
alreadybeencoveredindetail.Thereisnoneedtoreinstallanythinglistedherethatwas
describedthereaswell.
1. First,we'llinstallallthedependenciesneededforworkingwithapure
ReactNativeapp,startingwiththeHomebrew(https://brew.sh/)package
managerformacOS.Asstatedontheproject'shomepage,Homebrewcan
beeasilyinstalledfromtheTerminalviathefollowingcommand:
/usr/bin/ruby-e"$(curl-fsSLhttps://raw.githubusercontent.com/Homebrew/install/master/install)"
2. OnceHomebrewisinstalled,itcanbeusedtoinstallthedependencies
neededforReactNativedevelopment:Node.jsandnodemon.Ifyou'rea
JavaScriptdeveloper,you'velikelyalreadygotNode.jsinstalled.Youcan
checkit'sinstalledviathefollowingcommand:
node-v
ThiscommandwilllisttheversionofNode.jsthat'sinstalled,ifany.Note
thatyouwillneedNode.jsversion8orhigherforReactNative
development.IfNode.jsisnotalreadyinstalled,youcaninstallitwith
Hombrewviathefollowingcommand:
brewinstallnode
3. Wealsoneedthenodemonpackage,whichReactNativeusesbehindthe
scenestoenablethingslikelivereloadduringdevelopment.Installnodemon
withHomebrewviathefollowingcommand:
brewinstallwatchman
4. We'llalsoofcourseneedtheReactNativeCLIforrunningthecommands
thatbootstraptheReactNativeapp.Thiscanbeinstalledgloballywithnpm
viathefollowingcommand:
npminstall-greact-native-cli
5. WiththeCLIinstalled,allittakestocreateanewpureReactNativeappis
thefollowing:
react-nativeinitname-of-project
Thiswillcreateanewprojectinanewname-of-projectdirectory.This
projecthasallNativecodeexposed,andrequiresXcodeforrunningthe
iOSappandAndroidStudioforrunningtheAndroidapp.Luckily,
installingXcodeforsupportingiOSReactNativedevelopmentisa
simpleprocess.ThefirststepistodownloadXcodefromtheAppStore
andinstallit.ThesecondstepistoinstalltheXcodecommand-linetools.
Todothis,openXcode,choosePreferences...fromtheXcodemenu,
opentheLocationspanel,andinstallthemostrecentversionfromthe
CommandLineToolsdropdown:
6. Unfortunately,settingupAndroidStudioforsupportingAndroidReact
Nativedevelopmentisnotascutanddry,andrequiressomeveryspecific
stepsforinstallingit.Sincethisprocessisparticularlyinvolved,andsince
thereissomelikelihoodthattheprocesswillhavechangedbythetimeyou
readthischapter,Irecommendreferringtotheofficialdocumentationfor
in-depth,up-to-dateinstructionsoninstallingallAndroiddevelopment
dependencies.TheseinstructionsarehostedatthefollowingURL:
https://facebook.github.io/react-native/docs/getting-started.html#java-developme
nt-kit
7. Nowthatalldependencieshavebeeninstalled,we'reabletorunourpure
ReactNativeprojectviathecommandline.TheiOSappcanbeexecuted
viathefollowing:
react-nativerun-ios
AndtheAndriodappcanbestartedwiththis:
react-nativerun-android
Eachofthesecommandsshouldstartuptheassociatedemulatorforthe
correctplatform,installournewapp,andruntheappwithinthe
emulator.Ifyouhaveanytroublewitheitherofthesecommandsnot
behavingasexpected,youmightbeabletofindananswerintheReact
Nativetroubleshootingdocs,hostedhere:
https://facebook.github.io/react-native/docs/troubleshooting.html#content
ExpoCLIsetup
TheExpoCLIcanbeinstalledusingtheTerminalwithnpmviathefollowing
command:
npminstall-gexpo
TheExpoCLIcanbeusedtodoallthegreatthingstheExpoGUIclientcando.
ForallthecommandsthatcanberunwiththeCLI,checkoutthedocshere:
https://docs.expo.io/versions/latest/workflow/expo-cli
UsingNativeBaseforcross-platform
UIcomponents
SimilartoBootstrapontheweb,NativeBaseisacollectionofReactNative
componentsforimprovingtheefficiencyofReactNativeappdevelopment.The
componentscoverawiderangeofusecasesforbuildingoutUIinNative
applications,includingActionSheets,Badges,Cards,Drawers,andgridlayouts.
NativeBaseisalibrarythatsupportsbothpureReactNativeapplications(those
createdwiththeReactNativeCLIviareact-nativeinit)andExpopowered
applications.InstructionsforinstallingNativeBaseintoonetypeofprojector
anotherisoutlinedintheGettingStartedsectionoftheNativeBase
documentation,hostedhere:
https://github.com/GeekyAnts/NativeBase#4-getting-started
Sincethisisthecase,we'lltakethisopportunitytooutlinebothscenariosin
theGettingreadysectionofthisrecipe.
Gettingready
Whichevermethodofbootstrappingyouuseforthisrecipe,we'llbekeeping
theHowtodoit...sectionoftherecipeasconsistentaspossible.Onedifference
thatwe'llneedtotakeintoaccountistheprojectnamingconventionofeachapp
creationmethod.PureReactNativeapplicationsarenamedinPascalcase
(MyCoolApp)andExpoapplicationsarenamedinkebabcase(my-cool-app).If
you'recreatingapureReactNativeapp,youcanusetheappnameNativeBase,and
ifyou'reusingExpoyoucannameitnative-base.
UsingapureReactNativeapp(React
NativeCLI)
Assumingyou'vefollowedtheintroductiontothischapter,youshouldalready
havetheReactNativeCLIinstalledglobally.Ifnot,goaheadanddosonow
withnpm:
npminstall-greact-native-cli
TocreateanewpureReactappwiththeCLI,we'llusethefollowingcommand:
react-nativeinitNativeBase
ThiscreatesanewpureReactNativeappinafoldercalledNativeBaseinthe
currentdirectory.Thenextstepistoinstalltherequiredpeerdependencies.
Let'scdintothenewNativeBasedirectoryandinstallthenative-basepackageusing
npm:
npminstallnative-base--save
Alternatively,youcanuseyarn:
yarnaddnative-base
Finally,wewillinstalltheNativedependencieswiththefollowingcommand:
react-nativelink
IfweopenuptheprojectinanIDEandlookatthefolderstructureofthispure
ReactNativeapp,we'llseeafewslightdifferencesfromtheExpoapplications
we'vebecomeaccustomedtoatthispoint.First,therepositoryhasaniosand
anandroidfolder,eachcontainingNativecodefortherespectiveplatform.There's
alsoanindex.jsfileattherootoftheprojectthatisnotincludedinanapp
bootstrappedwithExpo.InanappmadewithExpo,thisfilewouldbeobscured
away,justliketheiosandandroidfolders,asfollows:
import{AppRegistry}from'react-native';
importAppfrom'./App';
AppRegistry.registerComponent('NativeBase',()=>App);
ThissimplyservesasthebootstrappingprocessofyourReactNativeappat
runtime.AppRegistryisimportedfromthereact-nativepackages,themainApp
componentisimportedfromtheApp.jsfileattherootofthedirectory,and
theAppRegistrymethodregisterComponentiscalledwithtwoparameters:thenameof
ourapp(NativeBase),andananonymousfunctionthatreturnstheAppcomponent.
FormoreinformationonAppRegistry,youcanfindthedocumentationhere:
https://facebook.github.io/react-native/docs/appregistry.html
Oneotherminordifferenceistheexistenceoftwosetsofdevelopment
instructionsintheApp.jsboilerplatecode,displayingtheappropriatedev
instructionsthroughtheuseofthePlatformcomponent.
Remembertostopandthinkwheneveryouseeathird-partyReactNative
packagewhoseinstallationinstructionsincluderunningthefollowingcommand:
react-nativelink
ItisusuallysafetoassumeitisnotcompatiblewithanExpoappunless
explicitlystatedotherwise.InthecaseofNativeBase,wehaveanoptiontouse
eithersetup,solet'scovergettingstartedwithourotheroptionnext,
bootstrappingwithExpo.
UsinganExpoapp
SettingupNativeBaseinanappcreatedwithExpoisassimpleasinstallingthe
requireddependencieswithnpmoryarn.First,wecancreatetheappusing
theExpoCLIonthecommandline:
expoinitnative-base
Oncetheappiscreated,wecancdintoitandinstallthedependenciesfor
NativeBasewithnpm:
npminstallnative-base@expo/vector-icons--save
Alternatively,youcanuseyarn:
yarnaddnative-base@expo/vector-icons
WhenusingNativeBasewithExpo,theNativeBasedocumentationrecommends
loadingfontsasynchronouslywiththeExpo.Font.loadAsyncmethodin
thecomponentWillMountmethodintheApp.jscomponent.We'llcoverhowtodothis
intheappropriatestepintheHowtodoit...sectionofthisrecipe.Youcanstart
uptheappfromtheCLIwiththefollowingcommand:
expostart
Howtodoit...
1. We'llstartbyaddingtheimportswe'llbeusingintheAppcomponentin
App.js.Whilethisappwon'thavemuchfunctionality,wewillbeusinga
numberofcomponentsfromNativeBasetoseehowtheycanhelpimprove
yourworkflow,asfollows:
importReact,{Component}from'react';
import{View,Text,StyleSheet}from'react-native'
import{
Spinner,
Button,
Body,
Title,
Container,
Header,
Fab,
Icon,
}from'native-base';
2. Next,let'sdeclaretheAppclassanddefineastartingstateobject.We'llbe
addingaFABsectiontoshowhowNativeBaseletsyoueasilyaddfly-out
menubuttonstoyourapp.Wewilltrackwhetherthismenushouldbe
displayedornotwiththefabActiveBoolean.We'llalsousetheloading
Booleanlaterintherendermethod,asfollows:
exportdefaultclassAppextendsComponent{
state={
loading:true
fabActive:false
}
//Definedonfollowingsteps
}
3. YoumayrecallfromtheGettingreadysectionoftherecipe,ifyou're
developinganappwithExpo,NativeBasesuggestsloadingthefontsused
byNativeBaseviatheExpo.Font.loadAsyncfunction.InthecomponentWillMount
method,we'llinitializeandawaittheloadingofrequirefonts,thenset
theloadingpropertyonstatetofalse.Theloadingpropertywillbereferenced
intherendermethodtodeterminewhethertheapphasfinishedloading,as
follows:
//Otherimportstatements
import{Font,AppLoaded}from'expo';
exportdefaultclassAppextendsComponent{
state={
fabActive:false
}
asynccomponentWillMount(){
awaitFont.loadAsync({
'Roboto':require('native-base/Fonts/Roboto.ttf'),
'Roboto_medium':require('native-base/Fonts/Roboto_medium.ttf'),
'Ionicons':require('@expo/vector-icons/fonts/Ionicons.ttf'),
});
this.setState({loading:false});
}
//Definedonfollowingsteps
}
4. SincethisappismostlyUI,we'rereadytostartbuildingtherenderfunction.
Tomakesurefontsareloadedbeforeweusethem,wereturntheApp
placeholderExpocomponent,AppLoading,iftheloadingpropertyofstateis
true,otherwisewe'llrendertheAppUI.AppLoadingwillinstructtheappto
continuedisplayingtheapp'ssplashscreenuntilthecomponentisremoved.
IfyouchosetostartthisrecipewithapureReactNativeproject,youwon'thave
accesstoExpocomponents.YoucansimplyreturnanemptyViewinsteadofAppLoadingin
thiscase.
5. We'llstartwiththeContainercomponent,alongwiththeHeader,Body,andTitle
helpercomponents.Thiswillactasthecontainerforthepage,displayinga
headeratthetopofthepagewiththetitleHeaderTitle!
render(){
if(this.state.loading){
return<AppLoading/>;
}else{
return(
<Container>
<Header>
<Body>
<Title>HeaderTitle!</Title>
</Body>
</Header>
</Container>
);
}
}
Atthispoint,theappshouldlooksimilartothefollowingscreenshot:
6. Inthefollowingcode,theHeaderwillhaveafewmoreUIelementsfrom
NativeBase.TheSpinnercomponentallowsforeasilydisplayingaloading
spinnerwiththedesiredcolorpassedinasaprop.TheButtoncomponent
providesbuttonswithmorebuilt-incustomizabilitywhencomparedwith
thevanillaTouchableOpacitycomponent.Here,we'reusingtheblockpropto
spreadthebuttonsacrosstheircontainer,andaninfoandsuccesspropon
eachtoapplytheirrespectivedefaultblueandgreenbackgroundcolors:
<Container>
<Header>
<Body>
<Title>HeaderTitle!</Title>
</Body>
</Header>
<Viewstyle={styles.view}>
<Spinnercolor='green'style={styles.spinner}/>
<Buttonblockinfo
onPress={()=>{console.log('button1pressed')}}
>
<Textstyle={styles.buttonText}>ClickMe!</Text>
</Button>
<Buttonblocksuccess
onPress={()=>{console.log('button2pressed')}}
>
<Textstyle={styles.buttonText}>NoClickMe!</Text>
</Button>
{this.renderFab()}
</View>
</Container>
7. TheprecedingrenderfunctionalsoreferstoarenderFabmethodwehavenot
yetdefined.ThismakesuseoftheIconandFabcomponents.NativeBase
usesthesamevector-iconspackageasExpounderthehood(defaultingto
Ioniconfontsifnotypepropisprovided),whichwascoveredintheUsing
FonticonsrecipeinChapter3,ImplementingComplexUserInterfaces–Part
I,sopleaserefertothatrecipeformoreinformation:
renderFab=()=>{
return(
<Fabactive={this.state.fabActive}
direction="up"
style={styles.fab}
position="bottomRight"
onPress={()=>this.setState({fabActive:
!this.state.fabActive})}>
<Iconname="share"/>
<Buttonstyle={styles.facebookButton}
onPress={()=>{console.log('facebookbuttonpressed')}}
>
<Iconname="logo-facebook"/>
</Button>
<Buttonstyle={styles.twitterButton}
onPress={()=>{console.log('twitterbuttonpressed')}}
>
<Iconname="logo-twitter"/>
</Button>
</Fab>
);
}
8. Let'sroundthisrecipeoutwithafewstylestoalignthingswithintheView
andapplycolorstoourlayout,asfollows:
conststyles=StyleSheet.create({
view:{
flex:1,
backgroundColor:'#fff',
alignItems:'center',
justifyContent:'center',
paddingBottom:40
},
buttonText:{
color:'#fff'
},
fab:{
backgroundColor:'#007AFF'
},
twitterButton:{
backgroundColor:'#1DA1F2'
},
facebookButton:{
backgroundColor:'#3B5998'
},
spinner:{
marginBottom:180
}
});
9. Lookingbackatthecompletedapp,there'snowanicespreadofUIthatis
cross-platformandeasytouse:
Howitworks...
Whilethemorecomplicatedportionofthisrecipewastheset-upoftheapp
itself,wehadaquickreviewofafewofthecomponentsprovidedby
NativeBasethatmightbeabletohelpyoudevelopyournextappmore
efficiently.Ifyouprefertoworkinawidget-basedsystemsimilartowhat
Bootstrap(https://getbootstrap.com/)orSemantic-UI(https://semantic-ui.com/)
provideonthewebplatform,besuretogiveNativeBaseaspin.Formore
informationonallofthecomponentsthatNativeBaseoffersandhowtouse
them,youcanfindtheofficialdocumentationathttp://docs.nativebase.io/Components
.html.
Usingglamorous-nativeforstylingUI
components
AsaJavaScriptdeveloper,you'relikelyfamiliarwithCSSonthewebandhow
it'susedtostylewebpagesandwebapplications.Morerecently,atechnique
calledCSS-in-JShascamealonginwebdevelopment,whichusesthepowerof
JavaScripttoadaptCSSforamoremodular,component-basedstylingapproach.
OneofthemainbenefitsofCSS-in-JStoolsistheirabilitytoproducestylesthat
arescopedtoagivenelement,insteadofthedefaultcascadingbehaviorof
vanillaJavaScript.ScopedCSSallowsadevelopertoapplystylesinamore
predictableandmodularway.Thisinturnincreasesusabilityinlarger
organizationsandmakespackagingandpublishingstyledcomponentseasier.If
you'dliketolearnmoreabouthowCSS-in-JSworksorwhereCSS-in-JScomes
fromconceptually,I'vewrittenanarticleonthetopiconthegitconnected
MediumblogcalledABriefHistoryofCSS-in-JS:HowWeGotHereandWhere
We'reGoing,hostedat:
https://levelup.gitconnected.com/a-brief-history-of-css-in-js-how-we-got-here-and-where-we
re-going-ea6261c19f04.
TheStyleSheetcomponentthatcomespackagedwithReactNativeisan
implementationofCSS-in-JS.OneofthemostpopularimplementationsofCSS-
in-JSonthewebisglamorous,alibrarycreatedbythevenerableKentC.Dodds.
ThislibraryinspiredtheexcellentReactNativestylinglibraryglamorous-native,
whichwewillbeusinginthisrecipe.
Gettingready
We'llneedtocreateanewappforthisrecipe.Thispackagedoesnotrequire
runningthefollowingcommandduringsetup:
react-nativelink
So,shouldworkjustfinewithanExpoapp.Let'snametherecipeglamorous-app.
Wewillalsoneedtoinstalltheglamorous-apppackage.Thiscanbeinstalled
withnpm:
npminstall--saveglamorous-native
Or,wecanuseyarn:
yarnaddglamorous-native
Howtodoit...
1. Let'sstartbyimportingallthedependencieswe'llneedinApp.js,asfollows:
importReactfrom'react';
importglamorousfrom'glamorous-native';
2. OurappwillneedacontainingViewelementtoholdalloftheother
componentsdisplayedintheapp.Insteadofstylingthiselementwithan
objectpassedtotheStyleSheetcomponent,likewe'vebeendoinginall
previousrecipes,we'lluseglamorousbypassingastyleobjecttotheview
method,whichreturnsastyledViewcomponentthatwestoreinaconst
calledContainerforlateruse,asfollows:
constContainer=glamorous.view({
flex:1,
justifyContent:'center',
alignItems:'center',
backgroundColor:'#fff',
});
3. Similarly,we'lladdthreestyledTextcomponentsusingglamorous.text.By
doingthis,wehavethreemorestyledandexplicitlynamedcomponents
readytobeusedinrender,asfollows:
constHeadline=glamorous.text({
fontSize:30,
paddingBottom:8
});
constSubHeading=glamorous.text({
fontSize:26,
paddingBottom:8
});
constButtonText=glamorous.text({
fontSize:18,
color:'white'
});
4. We'llalsomakeareusableButtoncomponentwith
theglamorous.touchableHighlightmethod.Thismethodshowshowglamorous
componentscanalsobecreatedwithmultiplestyledeclarationsofdifferent
types.ThesecondparameterpassedtotouchableHighlightinthiscaseisa
functionthatupdatesthebackgroundColorstyledependingonthepropsdefined
ontheelement,asfollows:
constButton=glamorous.touchableHighlight(
{padding:10},
props=>({backgroundColor:props.warning?'red':'blue'})
);
5. Wecanalsocreatecomponentsstyledinline,thankstothespecialversions
ofReactNativecomponentsglamorousshipswith.WewilluseanImage
component,butinsteadofimportingitfromreact-native,weusetheImage
componentfromtheimportedglamorouspackage,asfollows:
const{Image}=glamorous;
6. Now,wearereadytodeclaretheAppcomponent.Appwillonlyneedarender
functionforrenderingallournewlystyledcomponents,asfollows:
exportdefaultclassAppextendsReact.Component{
render(){
//Definedinfollowingsteps.
}
}
7. Let'sbeginbuildingouttherenderfunctionbyaddingtheContainer
componentwecreatedinstep2.Theimprovementincodereadabilityis
alreadyapparent.TheContainerisexplicitlynamedandneedsnoother
attributesorpropertiestodeclarestyles,asfollows:
render(){
return(
<Container>
//Definedonfollowingsteps
</Container>
);
}
8. Let'saddtheImagecomponentthatwepulledfromtheimportedglamorous
libraryinstep5.Noticehowweareabletodeclarestylepropertiessuch
asheight,width,andborderRadiusaspropsdirectlyonthecomponent,unlike
thevanillaImagecomponent:
<Container>
<Image
height={250}
width={250}
borderRadius={20}
source={{uri:'http://placehold.it/250/3B5998'}}
/>
//Definedonfollowingsteps
</Container>
9. Now,we'lladdtheHeadlineandSubheadingcomponentswecreatedinstep3.
JustliketheContainercomponent,thesetwocomponentsreadmuchmore
clearlythanoneViewandtwoTextelementsevercould:
<Container>
<Image
height={250}
width={250}
borderRadius={20}
source={{uri:'http://placehold.it/250/3B5998'}}
/>
<Headline>Iamaheadline</Headline>
<SubHeading>Iamasubheading</SubHeading>
//Definedinfollowingsteps
<Container>
10. Finally,we'lladdtheButtoncomponentwecreatedinstep4,andthe
ButtonTextcomponentwecreatedinstep3.BothbuttonshaveanonPress
methodlikeanyTouchableOpacityorTouchableHighlightcomponentwould,but
thesecondButtonalsohasawarningprop,causingittohavearedbackground
insteadofblue:
<Button
onPress={()=>console.log('Thanksforclickingme!')}
>
<ButtonText>
ClickMe!
</ButtonText>
</Button>
<Button
warning
onPress={()=>console.log(`Youshouldn'thaveclickedme!`)}
>
<ButtonText>
Don'tClickMe!
</ButtonText>
</Button>
11. Allofourglamorouscomponentshavebeenaddedtotherendermethod.Ifyou
runtheapp,youshouldbegreetedbyafullystyledUI.
Howitworks...
Instep2andstep3,wecreatedstyledViewandTextcomponentsbyusingthe
correspondingglamorousmethodandpassinginanobjectcontainingallthestyles
thatshouldbeappliedtothatparticularcomponent.
Instep4,wecreatedareusableButtonstyledcomponentbyapplyingthesame
methodusedforcreatingtheViewandTextcomponentsinprevioussteps.The
waystylesaredeclaredinthiscomponentisdifferent,however,andshowsoff
theversatilityglamorous-nativehaswhenprocessingstyles.Youcanpassany
numberofstylecollectionsasparameterstoaglamorouscomponentconstructor
andtheywillallbeapplied.Thisincludesdynamicstyles,whichusuallytakethe
formofusingpropsdefinedonthecomponenttoapplydifferentstyles.Instep
10,weusedourButtonelement.Ifthepropwarningispresent,asitisonthefirst
Buttoninrender,thebackgroundColorwillbered.Otherwise,itwillbeblue.This
providesaverynicesystemforapplyingsimpleandreusablethemingacross
multipletypesofcomponents.
Instep5,wepulledtheImagecomponentfromtheglamorouslibrarytouseinplace
oftheReactNativeImagecomponent.Thisspecialversionofthecomponent
behavesthesameasitsReactNativecounterpart,alongwiththebenefitofbeing
abletoapplystylesdirectlytotheelementitself.Instep8,whereweusedthat
component,wewereabletoapplyheight,width,andborderRadiusstyleswithout
everhavingtousethestyleprop.
Usingreact-native-spinkitforadding
animatedloadingindicators
Nomatterwhatkindofappyouarebuilding,there'saverygoodchanceyour
appwillneedtowaitondataofonekindoranother,whetheritbeloadingassets
orwaitingonaresponsefromanAJAXrequest.Whenthissituationarises,
you'llprobablyalsowantawayforyourapptoindicatetotheuserthatsome
requiredpieceofdataisstillloading.Oneeasy-to-usesolutiontothisproblemis
usingreact-native-spinkit.Thispackageprovides15(fourofwhichareiOS-only)
professionallooking,easy-to-useloadingindicatorsfordisplayingwhiledatais
loadinginyourapp.
Thispackagerequiresthefollowingcommandtoberun:
react-nativelink
So,iisprobablysafetoassumethatitwillnotworkwithanExpoapp(unless
thatappissubsequentlyejected/detached).Thiswillprovideuswithanother
recipethatdependsonapureReactNativeworkflow.
Gettingstarted
Nowthatwe'veestablishedthatthisrecipewillbebuiltinpureReactNative,we
canbeginbyinitializinganewappfromthecommandlinenamedSpinKitAppas
follows:
react-nativeinitSpinKitApp
Thiscommandwillbeginthescaffoldingprocess.Onceithascompleted,cdinto
thenewSpinKitAppdirectoryandaddreact-nativespinkitwithnpm:
npminstallreact-native-spinkit@latest--save
Oruseyarn:
yarnaddreact-native-spinkit@latest
Withthelibraryinstalled,wemustlinkitbeforeitcanbeusedwiththe
command:
react-nativelink
Atthispoint,theappisbootstrappedandthedependencieshavebeeninstalled.
TheappcanthenberunintheiOSorAndroidsimulatorsviathis:
react-nativerun-ios
Or,usethis:
react-nativerun-android
WhenlaunchingapureReactNativeprojectintheiOSsimulator,ifyouwishtospecifya
device,youcanpassthesimulatorargumentsettoastringvalueforthedesireddevice.For
example,react-nativerun-ios--simulator="iPhoneX"willlaunchtheappinasimulatediPhoneX.
WhenlaunchingapureReactNativeprojectinanAndroidemulatorviathecommandline,
youmustopentheAndroidemulatoryouintendtousebeforerunningthiscommand.
We'llalsobemakinguseoftherandomcolorlibraryagaininthisrecipe.Installit
withnpm:
npminstallrandomcolor--save
Oruseyarn:
yarnaddrandomcolor
Howtodoit...
1. We'llstartbyaddingthedependenciestotheApp.jsfileintherootofthe
project,asfollows:
importReact,{Component}from'react';
import{
StyleSheet,
View,
TouchableOpacity,
Text
}from'react-native';
importSpinnerfrom'react-native-spinkit';
importrandomColorfrom'randomcolor';
2. We'regoingtobesettinguptheappinthisrecipetocyclethroughallofthe
loadingspinnertypesprovidedbyreact-native-spinkit.Todothis,let'screate
anarraywithstringsforeachpossibletypeofspinner.Sincethelastfour
typesarenotfullysupportedinAndroid,theywillallappearasthesame
PlanespinneronAndroid,asfollows:
consttypes=[
'Bounce',
'Wave',
'WanderingCubes',
'Pulse',
'ChasingDots',
'ThreeBounce',
'Circle',
'9CubeGrid',
'FadingCircleAlt',
'FadingCircle',
'CircleFlip',
'WordPress',
'Arc',
'ArcAlt'
];
3. Now,wecanbeginbuildingtheAppcomponent.Wewillneedastateobject
withfourproperties:anisVisiblepropertytotrackwhetherthespinner
shouldbedisplayed,atypepropertyforholdingthecurrentspinnertype,a
typeIndexforkeepingourplaceinthetypesarray,andacolor.We'llinitialize
colortoarandomhexcodebysimplycallingrandomColor(),asfollows:
exportdefaultclassAppextendsComponent{
state={
isVisible:true,
typeIndex:0,
type:types[0],
color:randomColor()
}
}
4. We'llneedafunctionforchangingthepropertiesoftheSpinnercomponent,
whichwewilldefinelaterintherendermethod.Thisfunctionsimply
increasesthetypeIndexbyone,orsetsitbackto0iftheendofthearrayhas
beenreached,thenupdatesstateaccordingly,asfollows:
changeSpinner=()=>{
const{typeIndex}=this.state;
letnextType=typeIndex===types.length-1?0:typeIndex+
1;
this.setState({
color:randomColor(),
typeIndex:nextType,
type:types[nextType]
});
}
5. TherendermethodwillbemadeupoftheSpinnercomponent,wrappedin
aTouchableOpacitycomponentforchangingthetypeandcolorofSpinner.We
willalsoaddaTextcomponentfordisplayingthecurrentSpinnertype,as
follows:
render(){
return(
<Viewstyle={styles.container}>
<TouchableOpacityonPress={this.changeSpinner}>
<Spinner
isVisible={this.state.isVisible}
size={120}
type={this.state.type}
color={this.state.color}
/>
</TouchableOpacity>
<Textstyle={styles.text}>{this.state.type}</Text>
</View>
);
}
6. Finally,let'saddafewstylestothecentercontentandincreasethefontsize
oftheTextelementviathetextclass,asfollows:
conststyles=StyleSheet.create({
container:{
flex:1,
justifyContent:'center',
alignItems:'center',
backgroundColor:'#fff',
},
text:{
paddingTop:40,
fontSize:25
}
});
7. Withtherecipecomplete,weshouldseealoaderthatchangesonpress.
Thankstoreact-native-spinkit,thisisallittakestoaddslickloading
indicatorstoourReactNativeapplications!
Howitworks...
Instep5,wedefinedtheapp'srendermethod,wherewemadeuseoftheSpinner
component.TheSpinnercomponenthasfouroptionalprops:
isVisible:ABooleanthatdetermineswhetherthecomponentshouldbe
displayed.Default:true
color:Ahexcodetodeterminethespinner'scolor.Default:#000000
size:Determineswhatsizethespinnershouldbe,inpixels.Default:37
type:Astringthatdeterminesthetypeofspinnertouse.Default:Plane
SincetheisVisiblepropontheSpinnercomponentissettothevalueofisVisibleon
thestateobject,wecansimplytogglethispropertytotruewheneveralong
runningprocessbegins(suchaswaitingontheresponsefromanAJAXrequest),
andsetitbacktofalsewhentheoperationcompletes.
There'smore...
Eventhoughtheappwe'vecreatedinthisrecipeisfairlysimple,ithasillustrated
bothhowreact-native-spinkitcanbeimplemented,andhowusingthird-party
packagesthatrequirethereact-nativelinkcommandworksinpractice.Thereare
allkindsofthird-partypackagesavailabletouseinyournextReactNativeapp,
thankstothehardworkofcountlessopensourcecontributors.Beingequippedto
utilizeanythird-partypackagethatsuitsyourapp'sneeds,nomatterwhat
requirementsthosepackagehave,willbeavitaltoolinplanninganddeveloping
ReactNativeprojects.
Usingreact-native-side-menufor
addingsidenavigationmenus
SidemenusareacommonUXpatternfordisplayingoptions,controls,app
settings,navigation,andothersecondaryinformationinmobileapplications.
Thereact-native-side-menuthird-partypackageprovidesanexcellent,
straightforwardwaytoimplementsidemenusinaReactNativeapp.Inthis
recipe,wewillbebuildinganappthathasasidemenuhousingbuttonsthat
changethebackground.
Gettingready
Settingupthereact-native-side-menupackagedoesnotrequirethecommand:
react-nativelink
SofeelfreetocreatethisappwithExpoorasapureReactNativeapp.Weneed
tocreateanewappforthisrecipe,andforprojectnamingpurposeswe'llassume
thisappisbeingbuiltwithExpoandnameitside-menu-app.Ifyou'reusingpure
ReactNative,youcannameitSideMenuApp.
Wewillalsoneedtoinstallreact-native-side-menuintoourprojectwithnpm:
npminstallreact-native-side-menu--save
Or,useyarn:
yarnaddreact-native-side-menu
Howtodoit...
1. Let'sstartthisrecipebyaddingalltheimportswe'llneedintheApp.jsfilein
therootoftheproject.OneoftheseimportsisaMenucomponent,which
we'llcreateinalaterstep:
importReactfrom'react';
import{StyleSheet,Text,View,TouchableOpacity}from'react-native';
importSideMenufrom'react-native-side-menu';
importMenufrom'./components/Menu';
2. Next,let'sdefinetheAppclassandtheinitialstate.stateonlyneedstwo
propertiesinthisapp:anisOpenBooleantokeeptrackofwhentheside
menushouldbeopen,andaselectedBackgroundColorpropertywhosevalueisa
stringrepresentingthecurrentlyselectedbackgroundcolor,asfollows:
exportdefaultclassAppextendsReact.Component{
state={
isOpen:false,
selectedBackgroundColor:'green'
}
//Definedinfollowingsteps
}
3. OurappwillneedamethodforchangingtheselectedBackgroundColorproperty
onstate.Thismethodtakesacolorstringasaparameter,andsetsthatcolor
toselectedBackgroundColor.Itwillalsosetstate.isOpentofalsesothattheside
menucloseswhenacolorisselectedfromthemenu,asfollows:
changeBackgroundColor=color=>{
this.setState({
isOpen:false,
selectedBackgroundColor:color,
});
}
4. We'rereadytodefinetherendermethodApp.First,let'ssetuptheMenu
componentsoitcanbeusedbySideMenuinthenextstep.Westillhaven't
createdtheMenucomponent,butwe'llbeusinganonColorSelectedpropertyto
passalongthechangeBackgroundColormethod,asfollows:
render(){
constmenu=<MenuonColorSelected={this.changeBackgroundColor}
/>;
//Definedinnextstep
}
5. TherenderedUIconsistsoffourpieces.ThefirstisaViewcomponent,
whichhasastylepropertytiedtostate.selectedBackgroundColor.ThisView
componentholdsasingleTouchableOpacitybuttoncomponent,whichopens
thesidemenuwheneverit'spressed.TheSideMenucomponenthasarequired
menuprop,whichtakesthecomponentthatwillactasthesidemenuitself,
andsowe'llpasstheMenucomponenttothisproperty,asfollows:
render(){
constmenu=<MenuonColorSelected={this.changeBackgroundColor}/>;
return(
<SideMenu
menu={menu}
isOpen={this.state.isOpen}
onChange={(isOpen)=>this.setState({isOpen})}
>
<Viewstyle={[
styles.container,
{backgroundColor:this.state.selectedBackgroundColor}
]}>
<TouchableOpacity
style={styles.button}
onPress={()=>this.setState({isOpen:true})}
>
<Textstyle={styles.buttonText}>OpenMenu</Text>
</TouchableOpacity>
</View>
</SideMenu>
);
}
6. Asthefinaltouchforthiscomponent,let'saddbasicstylestocenterthe
layout,andapplycolorsandfontsizes,asfollows:
conststyles=StyleSheet.create({
container:{
flex:1,
alignItems:'center',
justifyContent:'center',
},
button:{
backgroundColor:'black',
padding:20,
borderRadius:10
},
buttonText:{
color:'white',
fontSize:25
}
});
7. It'stimetocreatetheMenucomponent.Let'screateacomponentfolderwith
aMenu.jsfileinside.We'llstartwiththecomponentimports.Aswe'vedone
inpreviousrecipes,we'llalsouseDimensionstostorethedimensionsofthe
appwindowinavariableforapplyingstyles,asfollows:
importReactfrom'react';
import{
Dimensions,
StyleSheet,
View,
Text,
TouchableOpacity
}from'react-native';
constwindow=Dimensions.get('window');
8. TheMenucomponentneedsonlytobeapresentationalcomponent,sinceit
hasnostateorneedforlifecyclehooks.Thecomponentwillreceive
onColorSelectedasaproperty,whichwe'llmakeuseofinthenextstep,as
follows:
constMenu=({onColorSelected})=>{
return(
//Definedonnextstep
);
}
exportdefaultMenu;
9. ThebodyoftheMenucomponentissimplyalistofTouchableOpacitybuttons
that,whenpressed,callonColorSelected,passinginthecorrespondingcolor,
asfollows:
<Viewstyle={styles.menu}>
<Textstyle={styles.heading}>SelectaColor</Text>
<TouchableOpacityonPress={()=>onColorSelected('green')}>
<Textstyle={styles.item}>
Green
</Text>
</TouchableOpacity>
<TouchableOpacityonPress={()=>onColorSelected('blue')}>
<Textstyle={styles.item}>
Blue
</Text>
</TouchableOpacity>
<TouchableOpacityonPress={()=>onColorSelected('orange')}>
<Textstyle={styles.item}>
Orange
</Text>
</TouchableOpacity>
<TouchableOpacityonPress={()=>onColorSelected('pink')}>
<Textstyle={styles.item}>
Pink
</Text>
</TouchableOpacity>
<TouchableOpacityonPress={()=>onColorSelected('cyan')}>
<Textstyle={styles.item}>
Cyan
</Text>
</TouchableOpacity>
<TouchableOpacityonPress={()=>onColorSelected('yellow')}>
<Textstyle={styles.item}>
Yellow
</Text>
</TouchableOpacity>
<TouchableOpacityonPress={()=>onColorSelected('purple')}>
<Textstyle={styles.item}>
Purple
</Text>
</TouchableOpacity>
</View>
10. Let'saddafewstylestolayouttheMenucomponent,applycolors,andapply
fontsizes.Notethatwe'realsousingthewindowvariablewedefinedinstep7
tosettheheightandwidthofthecomponentequaltothatofthescreen,as
follows:
conststyles=StyleSheet.create({
menu:{
flex:1,
width:window.width,
height:window.height,
backgroundColor:'#3C3C3C',
justifyContent:'center',
padding:20,
},
heading:{
fontSize:22,
color:'#f6f6f6',
fontWeight:'bold',
paddingBottom:20
},
item:{
fontSize:25,
paddingTop:10,
color:'#f6f6f6'
}
});
11. Ourappiscomplete!WhentheOpenMenubuttonispressed,asmoothly
animatedsidemenuwillslideoutfromtheleft,displayingalistofcolors
fortheusertochoosefrom.Whenacolorisselectedfromthelist,the
backgroundcoloroftheappchangesandthemenuslidesbacktoclosed:
Howitworks...
Instep4,wecreatedtherenderfunctionforthemainAppcomponent.Westored
theMenucomponentinamenuvariablesothatitcanbelegiblypassedtothemenu
propertyofSideMenu,aswedidinstep5.WepassthechangeBackgroundColor
classmethodviatheonColorSelectedproponourMenucomponentsothatwecan
useittoproperlyupdatestateintheAppcomponent.
WethenpasstheMenucomponenttoSideMenuasthemenuprop,whichwiresthetwo
componentstogether.ThesecondpropsisisOpen,whichdictateswhethertheside
menushouldbeopen.Thethirdprop,onChange,takesacallbackfunctionthat's
executedeverytimethemenuisopenedorclosed.TheonChangecallbackprovided
anisOpenparameterthatweusedtoupdatethevalueofisOpenonstatesothatit
staysinsync.
ThecontainingViewelementhasastylepropsettoanarraywithboththecontainer
stylesdefinedinstep6andanobjectwiththebackgroundColorkeyset
toselectedBackgroundColoronstate.ThiswillcausethebackgroundcoloroftheView
componenttochangetothisvaluewheneveritupdates.
Instep8andstep9,webuiltouttherendermethodoftheMenucomponent.
EachTouchableOpacitybuttoniswiredtocallonColorSelected,passinginthecolor
associatedwiththepressedbutton.ThisinturnrunschangeBackgroundColorinthe
parentAppclass,whichupdatesstate.selectedBackgroundColoronsettingstate.isOpen
tofalse,causingthebackgroundcolortochangeandthesidemenutoclose.
Usingreact-native-modalboxfor
addingmodals
AnothercommonpieceofmanymobileUIsisthemodal.Modalsaretheperfect
solutionforisolatingdatainameaningfulway,alertingauserofupdatedinfo,
displayingarequiredactionthatblocksotheruserinteractions(likealogin
screen),andsomuchmore.
Wewillbemakinguseofthethird-partypackagereact-native-modalbox.This
packageprovidesaneasy-to-understandandversatileAPIforcreatingmodals,
withoptionsincludingthefollowing:
position:Top,bottom,center
entry:Directionmodalentersfrom—toporbottom?
backdropColor
backdropOpacity
Foralloftheavailableoptions,refertothedocumentationat:
https://github.com/maxs15/react-native-modalbox
Gettingready
Wewillneedanewappforthisrecipe.Thereact-native-modalboxpackageisExpo
friendly,sowecancreatethisappwithExpo.We'llnamethisappmodal-app.If
usingapureReactNativeproject,anamesuchasModalAppwillwork,tomatch
namingconventions.
Wewillalsoneedthethird-partypackage.Itcanbeinstalledwithnpm:
npminstallreact-native-modalbox--save
Or,useyarn:
yarnaddreact-native-modalbox
Howtodoit...
1. Let'sstartbyopeningtheApp.jsfileintherootoftheprojectandaddthe
imports,asfollows:
importReactfrom'react';
importModalfrom'react-native-modalbox';
import{
Text,
StyleSheet,
View,
TouchableOpacity
}from'react-native';
2. Next,wewilldefineandexporttheAppcomponent,aswellastheinitial
stateobject,asfollows.Forthisapp,we'llonlyneedanisOpenBooleanfor
keepingtrackofwhetheroneofourmodalsshouldbeopenedorclosed:
exportdefaultclassAppextendsComponent{
state={
isOpen:false
};
//Definedonfollowingsteps
}
3. Let'sskipaheadtobuildingouttherendermethodnext.Thetemplateis
madeupoftwoTouchableOpacitybuttoncomponentsthatwhenpressed,open
theirrespectivemodal.We'llbedefiningthosetwomodalsinthefollowing
steps.ThesebuttonswillcalltwomethodsforrenderingeachModalofthe
twomodalcomponents,asfollows:
render=()=>{
return(
<Viewstyle={styles.container}>
<TouchableOpacity
onPress={this.openModal1}
style={styles.button}
>
<Textstyle={styles.buttonText}>
OpenModal1
</Text>
</TouchableOpacity>
<TouchableOpacity
onPress={this.openModal2}
style={styles.button}
>
<Textstyle={styles.buttonText}>
OpenModal2
</Text>
</TouchableOpacity>
{this.renderModal1()}
{this.renderModal2()}
</View>
);
}
4. Now,we'rereadytodefinetherenderModal1method.TheModalcomponent
needsarefproptobeassignedastring,whichwillbeusedtorefertothe
Modalwhenwewanttoopenorcloseit,asfollows:
renderModal1=()=>{
return(
<Modal
style={[styles.modal,styles.modal1]}
ref={'modal1'}
onClosed={this.onClose}
onOpened={this.onOpen}
>
<Textstyle={styles.modalText}>
HellofromModal1
</Text>
</Modal>
)
}
5. Let'saddtheopenModal1methodnext.Thisisthemethodthatiscalled
byonPressonthefirstTouchableOpacitycomponentweaddedintherender
methodinstep3.Bypassingthemodal1stringtotherefpropontheModal
componentwedefinedinstep4,we'reabletoaccessthemodal
asthis.refs.modal1.Callingtheopenmethodonthisrefopensthemodal.More
onthisintheHowitworks...sectionattheendofthisrecipe.Add
theopenModal1methodasfollows:
openModal1=()=>{
this.refs.modal1.open();
}
6. TheModalwedefinedinstep4alsohasonClosedandonOpenedprops,which
eachtakeacallbackthat'sexecutedwhenthemodalisclosedoropened,
respectively.Let'sdefinethecallbacksforthesepropsnext.Inthisrecipe,
we'lljustbefiringaconsole.logasaproofofconcept,asfollows:
onClose=()=>{
console.log('modalisclosed');
}
onOpen=()=>{
console.log('modalisopen');
}
7. We'rereadytodefinethesecondmodal.ThisModalcomponent'srefprop
willbesettothestringmodal2,andwe'lladdtwootheroptionalpropswe
didn'tuseontheothermodal.Thefirstisposition,whichcanbeset
totop,bottom,orcenter(default).TheisOpenpropprovidesasecondarywayof
openingandclosingamodalviaaBoolean.Thecontentofthemodalhas
aTouchableOpacitywithanOKbuttonthat,whenpressed,willsettheisOpen
Booleanonthestateobjecttofalse,closingthemodal,asfollows:
renderModal2=()=>{
return(
<Modal
style={[styles.modal,styles.modal2]}
ref={'modal2'}
position={'bottom'}
onClosed={this.onCloseModal2}
isOpen={this.state.isOpen}
>
<Textstyle={styles.modalText}>
HellofromModal2
</Text>
<TouchableOpacity
onPress={()=>this.setState({isOpen:false})}
style={styles.button}
>
<Textstyle={styles.buttonText}>
OK
</Text>
</TouchableOpacity>
</Modal>
)
}
8. Sincewe'reusingthestateBooleanisOpentomanipulatethestateofthe
modal,theopenModal2methodwillillustrateanalternativemethodfor
openingandclosingthemodal.BysettingisOpenonstatetotrue,thesecond
modalwillopen,asfollows:
openModal2=()=>{
this.setState({isOpen:true});
}
9. Youmighthavealsonoticedthatthesecondmodal,definedinstep7,hasa
differentonClosecallback.IftheuserpressestheOKbutton,theisOpenvalue
onstatewillbesuccessfullyupdatedtofalse,butiftheydismissthemodal
bytouchingthebackdrop,itwillnot.ThismethodguaranteesthattheisOpen
valueofthestateisproperlykeptinsyncnomatterhowtheuserdismisses
themodal,asfollows:
onCloseModal2=()=>{
this.setState({isOpen:false});
}
10. Thelaststepinthisrecipeisapplyingstyles.We'llhaveamodalclassfor
sharedmodalstyles,modal1andmodal2classesforstylesuniquetoeach
modal,andclassesforapplyingcolors,padding,andmargintobuttonsand
text,asfollows:
conststyles=StyleSheet.create({
container:{
backgroundColor:'#f6f6f6',
justifyContent:'center',
alignItems:'center',
flex:1
},
modal:{
width:300,
justifyContent:'center',
alignItems:'center'
},
modal1:{
height:200,
backgroundColor:"#4AC9B0"
},
modal2:{
height:300,
backgroundColor:"#6CCEFF"
},
modalText:{
fontSize:25,
padding:10,
color:'#474747'
},
button:{
backgroundColor:'#000',
padding:16,
borderRadius:10,
marginTop:20
},
buttonText:{
fontSize:30,
color:'#fff'
}
});
11. Thisrecipeiscomplete,andwenowhaveanappwithtwobasicmodals,
displayedonbuttonpress,andlivinginharmonyinthesamecomponent:
Howitworks...
Instep4,wedefinedthefirstModalcomponent.WedefinedtheonClosed
andonOpenedprops,passingtheonCloseandonOpenclassmethodstotheseprops.
WheneverthisModalcomponentisopened,this.onOpenwillfire,andthis.onClose
willexecutewhentheModalisclosed.Whilewedidn'tdoanythingexcitingwith
thesemethodsinthisrecipe,thesehookscouldserveastheperfectopportunity
forlogginguseractionsrelatedtothemodal.Orifthemodalhousesaform,
onOpencouldbeusedtopre-populatesomeforminputswithdata,andonClose
couldsavetheformdatatothestateobjectforuseasthemodalisclosed.
Instep5,wedefinedthemethodthatthefirstTouchableOpacitybuttoncomponent
executeswhenpressed:openModal1.Inthismethod,wemadeuseoftheModal
componentsref.RefsareacorefeatureofReactitself,andprovideaplaceon
thecomponentinstanceforstoringDOMnodesand/orReactelementsthatare
createdinthecomponent'srendermethod.JustasReact(andReactNative)
componentshavebothstateandprops(this.state,andthis.propsinaclass
component),theycanalsohaverefs(whichliveonthis.ref).Formoreonhow
refsinReactwork,checkthedocumentationat:
https://reactjs.org/docs/refs-and-the-dom.html
SincewesettherefproponthefirstModaltothestringmodal1,we'reabletoaccess
thissamecomponentintheopenModal1methodwiththereferencethis.ref.modal1.
SinceModalhasanopenandaclosemethod,callingthis.ref.modal1.open()opens
theModalwitharefofmodal1.
ThisisnottheonlywaytoopenandcloseaModalcomponent,asillustratedwith
thesecondmodalwedefinedinstep7.SincethiscomponenthasanisOpenprop,
themodalcanbeopenedorclosedbychangingtheBooleanvaluebeingpassed
totheprop.BysettingisOpentobetheisOpenvalueofthestate,wecanusethe
OKbuttoninthismodaltoclosethemodalfromwithin,bysettingisOpentofalse
onstate.Instep8,wedefinedtheopenModal2method,whichalsoillustrates
openingthesecondmodalbychangingthevalueofisOpenonstate.
Instep9,wedefinedaseparateisClosedcallbackforkeepingtheisOpenvalueof
stateinsyncincasetheuserdismissesthemodalbypressingthebackdrop
insteadofthemodal'sOKbutton.Analternativestrategywouldhavebeento
disabletheuser'sabilitytodismissthemodalviapressingthebackdrop,by
addingthebackdropPressToClosepropertytotheModalcomponentandsettingitto
false.
Thereareanumberofotheroptionalpropsprovidedbythereact-native-modalbox
packagethatcanmakemodalcreationeasier.Weusedpositioninthisrecipeto
declarethatthesecondmodalbeplacedatthebottomofthescreen,andyoucan
viewallotheravailablepropsforModalinthedocumentationat:
https://github.com/maxs15/react-native-modalbox
Thereact-native-modalboxlibrarysupportsmultiplemodalsinasinglecomponent;however,
attemptingtousetheisOpenproponmorethanoneofthesemodalswillcauseallofthose
modalstoopenatonce,whichisunlikelytobethedesiredbehavior.
AddingNativeFunctionality-PartI
Inthischapter,we'llcoverthefollowingrecipes:
ExposingcustomiOSmodules
RenderingcustomiOSviewcomponents
ExposingcustomAndroidmodules
RenderingcustomAndroidviewcomponents
HandlingtheAndroidbackbutton
Introduction
OneofthecoreprinciplesinReactNativedevelopmentiswritingJavaScriptto
buildtrulynativemobileapplications.Toaccomplishthis,manynativeAPIsand
UIcomponentsareexposedthroughanabstractionlayerandareaccessed
throughtheReactNativebridge.WhiletheReactNativeandExpoteams
continuetoimproveandexpandonthealreadyimpressiveAPIsthatcurrently
exist,throughthenativeAPIs,wecanaccessfunctionalitythatisn'tavailable
otherwise,suchasvibration,contacts,andnativealertsandtoasts.
Byexposingthenativeviewcomponents,we'reabletoleverageallofthe
renderingperformancethedevicehastooffer,aswe'renotgoingthrougha
WebViewasinahybridapp.Thisgivesanativelookandfeelthatadaptstothe
platformtheuserisrunningtheappon.WithReactNative,we'realreadyableto
rendermanynativeviewcomponentsincludingmaps,lists,inputfields,toolbars,
andpickers.
WhileReactNativecomeswithmanybuilt-innativemodulesandview
components,we'reofteninapositionwhereweneedsomecustomfunctionality
leveragingthenativeapplicationlayerthatisn'tprovidedoutofthebox.
Fortunately,there'sanextremelyrichopensourcecommunitysupportingReact
Nativethatnotonlycontributestothelibraryitself,butalsopublisheslibraries
thatexportsomecommonnativemodulesandviewcomponents.Ifyoucan't
findafirst-orthird-partylibrarytoaccomplishwhatyouneed,youcanalways
buildityourself.
Inthischapter,we'llcoverrecipesthatgooverexposingcustomnative
functionality,whetherit'sanAPIorviewcomponent,onbothplatforms.
Therewillbealotofgeneratedcodeinthenativeportionsofthecodewe'llbeusinginthese
recipes.Thecodeblocksprovidedthroughoutthischapterwill,likeinpreviouschapters,
continuetodisplayallofthecodeusedinaparticularstep,whetherit'saddedbyusor
generated,unlessstatedotherwise.Thisisintendedtoeasetheburdenofunderstandingthe
contextofapieceofcode,andfacilitatesthediscussionofthesepiecesofgeneratedcode
whenfurtherexplanationiswarranted.
ExposingcustomiOSmodules
AsyoubegindevelopingmoreinterestingandcomplexReactNative
applications,youcouldpossiblyreachapointwhereexecutingcertaincode
wouldbeonlypossible(orsignificantlyimproved)inthenativelayer.This
allowsforexecutingdataprocessingthat'sfasterinthenativelayerwhen
comparedwithJavaScript,andforaccessingcertainnativefunctionalitythatisn't
otherwiseexposed,suchasfileI/O,orleveragingexistingnativecodefrom
otherapplicationsorlibrariesinyourReactNativeapp.
Thisrecipewillwalkyouthroughtheprocessofexecutingsomenative
Objective-CorSwiftcodeandcommunicatingwiththeJavaScriptlayer.We'll
buildanativeHelloManagermodulethatwillgreetouruserwithamessage.We'll
alsoshowhowtoexecutenativeObjective-CandSwiftcode,takingin
arguments,andshowingseveralwaysofcommunicatingbackwiththeUI(or
JavaScript)layer.
Gettingready
Forthisrecipe,we'llneedanewempty,pureReactNativeapplication.Let'scall
itNativeModuleApp.
Inthisrecipe,we'llalsomakeuseofthereact-native-buttonlibrary.Thislibrary
willallowustoworkwithaButtoncomponentthat'smoresophisticatedthanthe
ReactNativecounterparts.Itcanbeinstalledwithnpm:
npminstallreact-native-button--save
Oritcanbeinstalledusingyarn:
yarnaddreact-native-button
Howtodoit...
1. We'llstartbyopeningtheiOSProjectinXcode.Theprojectfilehasan
.xcodeprojfileextensionandislocatedintheios/directoryintherootofthe
project.Inourcase,thefilewillbecalledNativeModuleApp.xcodeproj.
2. Weneedtomakeanewfilebyselectingandright-clickingonthe
group/folderthatmatchestheprojectname,thenclickingonNewFile...as
showninthefollowing:
3. We'llbemakingaCocoaclass,soselectCocoaClassandclickNext.
4. We'lluseHelloManagerfortheClassnameandsettheSubclassof
toNSObject,andtheLanguageasObjective-Casshowninthefollowing:
5. AfterclickingNext,we'llbepromptedtochoosethedirectoryforthenew
class.WewanttosaveittotheNativeModuleAppdirectory.
6. CreatingthisnewCocoaclasshasaddedtwonewfilestotheproject:a
headerfile(HelloManager.h)andanimplementationfile(HelloManager.m).
7. Insidetheheaderfile(HelloManager.h),youshouldseesomegeneratedcode
implementingthenewHelloManagerprotocol.Weneedtoimportthe
ReactRCTBridgeModulelibraryaswell.Thefileshouldultimatelylooklikethis:
#import<Foundation/Foundation.h>
#import<React/RCTBridgeModule.h>
@interfaceHelloManager:NSObject<RCTBridgeModule>
@end
8. Theimplementationfile(HelloManager.m)housesthefunctionalityofour
module.InorderforourReactNativeapptobeabletoaccessthismodule
fromtheJavaScriptlayer,weneedtoregisteritwiththeReactBridge.This
isdonebyaddingRCT_EXPORT_MODULE()afterthe@implementationtag.Alsonote
thattheheaderfileshouldalreadybeimportedintothisfileaswell:
#import"HelloManager.h"
@implementationHelloManager
RCT_EXPORT_MODULE();
@end
9. Weneedtoaddthefunctionwe'llbeexportingtotheReactNativeapp.
We'llcreateagreetUsermethodthatwilltaketwoarguments,nameandisAdmin.
Theseargumentswillbeusedtocreateagreetingmessageusingstring
concatenationandthensenditbacktotheJavaScriptlayerviacallback:
#import"HelloManager.h"
@implementationHelloManager
RCT_EXPORT_MODULE();
RCT_EXPORT_METHOD(
greetUser:(NSString*)nameisAdmin:(BOOL*)isAdmincallback:(RCTResponseSenderBlock)callback
){
NSString*greeting=
[NSStringstringWithFormat:
@"Welcome%@,you%@anadministrator.",name,isAdmin?@"are":@"arenot"];
callback(@[greeting]);
}
@end
10. We'rereadytoswitchovertotheJavaScriptlayer,whichwillhaveaUIthat
willinvokethenativeHelloManagergreetUsermethodwe'vejustcreated,then
displayitsoutput.Fortunately,theReactNativebridgedoesalloftheheavy
liftingforusandleavesuswithasimple-to-useJavaScriptobjectthat
mimicstheNativeModulesAPI.Inthisexample,we'llbeusingTextInputand
SwitchtoprovidenameandtheisAdminvalueforthenativemodulesmethod.
Let'sstartwithoutimportsinApp.js:
importReact,{Component}from'react';
import{
StyleSheet,
Text,
View,
NativeModules,
TextInput,
Switch
}from'react-native';
importButtonfrom'react-native-button';
11. WecanusetheNativeModulescomponentweimportedtogettheHelloManager
protocolwecreatedfromthenativelayer:
constHelloManager=NativeModules.HelloManager;
12. Let'screatetheAppcomponentanddefinetheinitialstateobject.We'lladd
agreetingMessagepropertyforsavingthemessagereceivedfromthenative
module,userNameforstoringtheenteredusername,andanisAdminBoolean
forrepresentingwhethertheuserisanadministrator:
exportdefaultclassAppextendsComponent{
state={
greetingMessage:null,
userName:null,
isAdmin:false
}
//Definedonfollowingsteps
}
13. We'rereadytostartbuildingtherendermethod.First,we'llneedaTextInput
componentforgettingausernamefromtheuser,andaSwitchcomponent
fortogglingtheisAdminstate:
render(){
return(
<Viewstyle={styles.container}>
<Textstyle={styles.label}>
EnterUserName
</Text>
<TextInput
ref="userName"
autoCorrect={false}
style={styles.inputField}
placeholder="UserName"
onChangeText={(text)=>this.setState({userName:text})}
/>
<Textstyle={styles.label}>
Admin
</Text>
<Switchstyle={styles.radio}
value={this.state.isAdmin}
onValueChange={(value)=>
this.setState({isAdmin:value})
}
/>
//Continuedbelow
</View>
);
}
14. TheUIwillalsoneedButtonforsubmittingthecallbacktothenativemodule
andaTextcomponentfordisplayingthemessagereturnedfromthenative
module:
render(){
return(
//Definedabove.
<Button
disabled={!this.state.userName}
style={[
styles.buttonStyle,
!this.state.userName?styles.disabled:null
]}
onPress={this.greetUser}
>
Greet(callback)
</Button>
<Textstyle={styles.label}>
Response:
</Text>
<Textstyle={styles.message}>
{this.state.greetingMessage}
</Text>
</View>
);
}
15. WiththeUIrenderingthenecessarycomponents,we'rereadytowireup
theonPresshandlerofButtontoacalltothenativelayer.Thisfunctionpasses
thedisplayResultsclassmethodasthethirdparameterasthecallbacktobe
usedbythenativegreetUserfunction.We'lldefinedisplayResultsinthenext
step:
greetUser=()=>{
HelloManager.greetUser(
this.state.userName,
this.state.isAdmin,
this.displayResults
);
}
16. displayResultswillneedtodotwothings:blurtheTextInputusingtherefs
associatedwiththecomponentandsetgreetingMessageonstatetotheresults
returnedfromthenativemodule:
displayResults=(results)=>{
this.refs.userName.blur();
this.setState({greetingMessage:results});
}
17. Thelaststepisaddingthestylestothelayoutandstylingtheapp:
conststyles=StyleSheet.create({
container:{
flex:1,
justifyContent:'center',
alignItems:'center',
backgroundColor:'#F5FCFF',
},
inputField:{
padding:20,
fontSize:30
},
label:{
fontSize:18,
marginTop:18,
textAlign:'center',
},
radio:{
marginBottom:20
},
buttonStyle:{
padding:20,
backgroundColor:'#1DA1F2',
color:'#fff',
fontSize:18
},
message:{
fontSize:22,
marginLeft:50,
marginRight:50,
},
disabled:{
backgroundColor:'#3C3C3C'
}
});
18. WenowhaveaworkingReactNativeappthat'sabletocommunicate
directlywiththenativeiOSlayer:
Howitworks...
Theappwebuiltinthisrecipewillserveasthefoundationformanyofthe
followingrecipesinthischapter.It'salsothemethodFacebookusesto
implementmanybundledReactNativeAPIs.
Thereareseveralimportantconceptstokeepinmindgoingforward.Anynative
moduleclasswewanttouseintheJavaScriptlayerhastoextendRCTBridgeModule,
asitcontainsfunctionalityforregisteringourclassontotheReactNativebridge.
WeregisterourclasswiththeRCT_EXPORT_MODULEmethodcall,whichregisters
methodsonthemoduleoncethemodulehasbeenregistered.Registeringthe
modulealongwithitsrespectivemethodsandpropertiesiswhatallowsusto
interfacewiththenativelayerfromtheJavaScriptlayer.
ThegreetUsermethodisexecutedwhenthebuttonispressed.Thisfunctionin
turnmakesacalltoHelloManager.greetUser,passingtheuserNameandisAdmin
propertiesfromstateandthedisplayResultsfunctionasacallback.The
displayResultssetsthenewgreetingMessageonstate,causingtheUItoberefreshed
andthemessagetobedisplayed.
There'smore...
ReactNativesupportsthreewaysforthenativemoduletocommunicatebackto
theJavaScriptlayer:callbacks(coveredinthisrecipe),promises,andevents.To
seeworkingexamplesofeachoneofthesetechniques,takealookatthesource
codebundledwiththisbook.Ifyou'reinterestedinwritingnativemodulesusing
theSwiftlanguage,there'saworkingexampleofthesamefunctionalitybundled
inthesampleapplication.
Seealso
AnexplanationofhowReactNativeapplicationsbootup:https://levelup.git
connected.com/wait-what-happens-when-my-react-native-application-starts-an-in-depth-
look-inside-react-native-5f306ef3250f
AdeepdiveintohowReactNativeeventsactuallywork:https://levelup.gitc
onnected.com/react-native-events-in-gory-details-what-happens-on-the-way-to-listener
s-2cee6c55940c
RenderingcustomiOSview
components
Whileit'sveryimportanttoleveragethedevices,processingpowerinexecuting
codeonthenativelayerinourReactNativeapplication,it'sequallyimportantto
leverageitsrenderingpowertoshownativeUIcomponents.ReactNativecan
renderanyUIcomponentthat'sanimplementationofUIViewinsidean
application.Thesecomponentscanbelists,formfields,tables,graphics,andso
on.
Forthisrecipe,wecreatedaReactNativeapplicationtitledNativeUIComponent.
Inthisrecipe,we'lltakeanativeUIButtonandexposeitasaReactNativeview
component.You'llbeabletosetthebuttonlabelandattachahandlerforwhen
it'stapped.
Howtodoit...
1. Let'sstartbyopeningtheiOSprojectinXcode.Theprojectfileislocated
intheios/directoryoftheprojectandshouldbe
calledNativeUIComponent.xcodeproj.
2. Selectandright-clickonthegroupthatmatchesyourprojectnameand
clickonNewFile...:
3. We'llbemakingaCocoaclass,soselectCocoaClassandclickNext.
4. We'llbecreatingabutton,solet'snametheClassButtonandsettheSubclass
oftoUIViewandtheLanguageasObjective-C:
5. AfterclickingNext,we'llbepromptedtochoosethedirectoryforthenew
class.WewanttosaveittotheNativeUIComponentdirectorytocreatetheclass.
6. We'realsogoingtoneedaButtonViewManagerclassaswell.Youcanrepeat
steps2to5withButtonViewManagerastheclassnameandRCTViewManagerasthe
subclass.
7. First,we'regoingtoimplementourButtonUIclass.Intheheader(Button.h)
file,we'llimportRCTComponent.hfromReactandaddanonTappropertytowire
upourtapevent:
#import<UIKit/UIKit.h>
#import"React/RCTComponent.h"
@interfaceButton:UIView
@property(nonatomic,copy)RCTBubblingEventBlockonTap;
@end
8. Let'sworkontheimplementationfile(Button.m).We'llstartbycreating
referencesforourUIButtoninstanceandthestringthatwillholdthebutton
label:
#import"Button.h"
#import"React/UIView+React.h"
@implementationButton{
UIButton*_button;
NSString*_buttonText;
}
//Definedinfollowingsteps
9. ThebridgewilllookforasetterforthebuttonTextproperty.Thisiswhere
we'llsettheUIButtoninstancetitlefield:
-(void)setButtonText:(NSString*)buttonText{
NSLog(@"Settext%@",buttonText);
_buttonText=buttonText;
if(_button){
[_buttonsetTitle:
buttonTextforState:UIControlStateNormal];
[_buttonsizeToFit];
}
}
10. OurButtonwillacceptanonTapeventhandlerfromtheReactNativeapp.We
needtowirethistoourUIButtoninstancethroughanactionselector:
-(IBAction)onButtonTap:(id)sender{
self.onTap(@{});
}
11. WeneedtoinstantiatetheUIButtonandplaceitinsideaReactSubview.We'll
callthismethodlayoutSubviews:
-(void)layoutSubviews{
[superlayoutSubviews];
if(_button==nil){
_button=
[UIButtonbuttonWithType:UIButtonTypeRoundedRect];
[_buttonaddTarget:selfaction:@selector(onButtonTap:)
forControlEvents:UIControlEventTouchUpInside];
[_buttonsetTitle:
_buttonTextforState:UIControlStateNormal];
[_buttonsizeToFit];
[selfinsertSubview:_buttonatIndex:0];
}
}
12. Let'simporttheReactRCTViewManagerintheButtonViewManager.hheaderfile:
#import"React/RCTViewManager.h"
@interfaceButtonViewManager:RCTViewManager
@end
13. NowweneedtoimplementourButtonViewManager,whichwillinterfacewith
ourReactNativeapplication.Let'sworkontheimplementationfile
(ButtonViewManager.m)tomakethishappen.WeuseRCT_EXPORT_VIEW_PROPERTYto
passalongthebuttonTextpropertyandonTapmethodtotheReactNative
layer:
#import"ButtonViewManager.h"
#import"Button.h"
#import"React/UIView+React.h"
@implementationButtonViewManager
RCT_EXPORT_MODULE()
-(UIView*)view{
Button*button=[[Buttonalloc]init];
returnbutton;
}
RCT_EXPORT_VIEW_PROPERTY(buttonText,NSString);
RCT_EXPORT_VIEW_PROPERTY(onTap,RCTBubblingEventBlock);
@end
14. WearereadytoswitchovertotheReactNativelayer.We'regoingtoneeda
customButtoncomponent,solet'screateanewcomponentsfolderintherootof
theprojectwithanewButton.jsfileinsideofit.We'llalsoneedtoimport
therequireNativeComponentcomponentfromReactNativeforinterfacingwith
ournativeUIcomponent:
importReact,{Component}from'react';
import{
StyleSheet,
Text,
View
}from'react-native';
importButtonfrom'./components/Button';
15. TheButtoncomponentwillgrabthenativeButtonmodulewecreatedearlier
viatherequireNativeComponentReactNativehelper.Thecalltakesastringtobe
usedasthecomponent'snameintheReactNativelayerasthefirst
parameter,andthesecondtakestheButtoncomponentinthefile,effectively
wiringthetwotogether:
exportdefaultclassButtonextendsComponent{
render(){
return<ButtonView{...this.properties}/>;
}
}
constButtonView=requireNativeComponent('ButtonView',Button);
16. We'rereadytobuildoutthemainAppcomponentintheApp.jsfileintheroot
oftheproject.We'llstartwiththeimports,whichwillincludetheButton
componentwecreatedinthelasttwosteps:
importReact,{Component}from'react';
import{
StyleSheet,
Text,
View
}from'react-native';
importButtonfrom'./components/Button';
17. Let'sdefinetheAppcomponentandtheinitialstateobject.Thecountproperty
willkeeptrackofthenumberoftimestheButtoncomponenthasbeen
pressed:
exportdefaultclassAppextendsComponent{
state={
count:0
}
//Definedonfollowingsteps
}
18. We'rereadytodefinetherendermethod,whichwilljustconsistoftheButton
component,alongwithaTextelementfordisplayingthecurrentbuttonpress
count:
render(){
return(
<Viewstyle={styles.container}>
<ButtonbuttonText="ClickMe!"
onTap={this.handleButtonTap}
style={styles.button}
/>
<Text>ButtonPressedCount:{this.state.count}</Text>
</View>
);
}
19. YoumayrecallthattheButtoncomponentwecreatedhasanonTapproperty,
whichtakesacallbackfunction.Inthiscasewe'lljustusethisfunctionto
increasethecounterthatlivesonstate:
handleButtonTap=()=>{
this.setState({
count:this.state.count+1
});
}
20. Let'swrapupthisrecipewithafewbasicstyles:
conststyles=StyleSheet.create({
container:{
flex:1,
justifyContent:'center',
alignItems:'center',
backgroundColor:'#F5FCFF',
},
button:{
height:40,
width:80
}
});
21. Theappiscomplete!Whenthebuttonispressed,thefunctionpassed
toonTapwillbeexecuted,increasingthecounterbyone:
Howitworks...
Inthisrecipe,weexposedabasicnativeUIcomponent.Thisisthesamemethod
bywhichalloftheUIcomponentsbuiltintoReactNative(forexample,Slider,
Picker,andListView)werecreated.
ThemostimportantrequirementincreatingUIcomponentsisthatyourViewManagerextends
RCTViewManagerandreturnsaninstanceofUIView.Inourcase,we'rewrappingUIButtonwitha
React-specificUIViewextension,whichimprovesourabilitytolayoutandstylethecomponent.
Thenextimportantfactorissendingpropertiesandreactingtocomponent
events.Instep13,weusedtheRCT_EXPORT_VIEW_PROPERTYmethodprovidedbyReact
NativetoregisterthebuttonTextandonTapviewpropertiesthatwillcomefromthe
JavaScriptlayertotheButtoncomponent.ThatButtoncomponentisthencreated
andreturnedtobeusedintheJavaScriptlayer:
-(UIView*)view{
Button*button=[[Buttonalloc]init];
returnbutton;
}
ExposingcustomAndroidmodules
Often,you'llfindtheneedforReactNativeapplicationstointerfacewithnative
iOSandAndroidcode.HavingdiscussedintegratingnativeiOSmodules,now
it'stimetocovertheequivalentrecipesinAndroid.
ThisrecipewilltakeusthroughwritingourfirstAndroidnativemodule.We're
goingtocreateaHelloManagernativemodulewithagreetUsermethodthattakesname
andanisAdminBooleanasarguments,whichwillreturnagreetingmessagethat
we'lldisplayintheUI.
Gettingready
Forthisrecipe,we'llneedtocreateanotherpureReactNativeapp.Let'sname
thisprojectNativeModuleAppaswell.
We'llalsobemakinguseofthereact-native-buttonlibraryagain,whichcanbe
installedwithnpm:
npminstallreact-native-button--save
Alternatively,itcanbeinstalledusingyarn:
yarnaddreact-native-button
Howtodoit...
1. We'llstartbyopeningthenewproject'sAndroidcodeinAndroidStudio.
FromtheAndroidStudiowelcomescreen,youcanselectOpenanexisting
AndroidStudioproject,thenselecttheandroiddirectoryinsideoftheproject
folder.
2. Oncetheprojecthasloaded,let'sopentheprojectexplorer(thatis,the
directorytree)ontheleftsideofAndroidStudioandexpandthepackage
structuretofindtheJavasourcefiles,whichshouldlivein
app/java/com.nativemoduleapp.Thefoldershouldalreadyhavetwo.javafilesin
it,MainActivityandMainApplication:
3. Right-clickonthecom.nativemoduleapppackage,selectNew|JavaClass,
andnametheclassHelloManager.Also,besuretosettheKindfieldtoClass:
4. We'llalsoneedaHelloPackageclassinthesamedirectory.Youcanrepeat
steps2and3tocreatethisclass,simplyapplyingthenewnameand
keepingtheKindfieldsettoClass.
5. Let'sstartbyimplementingourHelloManagernativemodule.We'llstartwith
thepackagenameandthedependencieswe'llneedinthisfile:
packagecom.nativemoduleapp;
importcom.facebook.react.bridge.Callback;
importcom.facebook.react.bridge.ReactApplicationContext;
importcom.facebook.react.bridge.ReactContextBaseJavaModule;
importcom.facebook.react.bridge.ReactMethod;
6. ReactContextBaseJavaModuleisthebaseclassforallReactNativemodules,so
we'llbecreatingtheHelloManagerclassasasubclassofit.Wealsoneedto
defineagetNamemethod,whichisusedforregisteringnativemoduleswith
theReactNativebridge.ThisisonedifferencefromtheiOSnativemodule
implementations,asthosearedefinedviaclassname:
publicclassHelloManagerextendsReactContextBaseJavaModule{
publicHelloManager(ReactApplicationContextreactContext){
super(reactContext);
}
@Override
publicStringgetName(){
return"HelloManager";
}
}
7. Nowthatwe'vesetupourHelloManagernativemodule,it'stimetoaddthe
greetUsermethodtoit,whichwillexpectasarguments,name,isAdmin,andthe
callbackthatwillbeexecutedtosendthemessagetotheReactNative
layer:
publicclassHelloManagerextendsReactContextBaseJavaModule{
//Definedinprevioussteps
@ReactMethod
publicvoidgreetUser(Stringname,BooleanisAdmin,Callbackcallback){
System.out.println("UserName:"+name+",Administrator:"+(isAdmin?"Yes":"No"));
Stringgreeting="Welcome"+name+",you"+(isAdmin?"are":"arenot")+"anadministrator";
callback.invoke(greeting);
}
}
8. Anotherstepthat'suniquetoAndroidishavingtoregisterthenative
modulewiththeapplication,whichisatwo-stepprocess.Thefirststepisto
addourHelloManagermoduletotheHelloPackageclasswecreatedearlier.We'll
startwiththedependenciesforHelloPackage.java:
packagecom.nativemoduleapp;
importcom.facebook.react.ReactPackage;
importcom.facebook.react.bridge.NativeModule;
importcom.facebook.react.bridge.ReactApplicationContext;
importcom.facebook.react.uimanager.ViewManager;
importjava.util.ArrayList;
importjava.util.Collections;
importjava.util.List;
9. TheimplementationofHelloPackagesimplyfollowsthepatternprovidedby
theofficialdocumentation(https://facebook.github.io/react-native/docs/native-m
odules-android.html).Themostimportantpiecehereisthecalltomodules.add,
whereanewinstanceofHelloManagerispassedinwithreactContextasits
parameter:
publicclassHelloPackageimplementsReactPackage{
@Override
publicList<ViewManager>createViewManagers(ReactApplicationContextreactContext){
returnCollections.emptyList();
}
@Override
publicList<NativeModule>createNativeModules(ReactApplicationContextreactContext){
List<NativeModule>modules=newArrayList<>();
modules.add(newHelloManager(reactContext));
returnmodules;
}
}
10. ThesecondstepinregisteringthenativemodulewiththeReactNativeapp
istoaddHelloPackagetotheMainApplicationmodule.Mostofthecodehereis
generatedbytheReactNativebootstrappingprocess.ThegetPackages
methodneedstobeupdatedtotakebothnewMainReactPackage()andnew
HelloPackage()asargumentspassedtoArrays.asList:
packagecom.nativemoduleapp;
importandroid.app.Application;
importcom.facebook.react.ReactApplication;
importcom.facebook.react.ReactNativeHost;
importcom.facebook.react.ReactPackage;
importcom.facebook.react.shell.MainReactPackage;
importcom.facebook.soloader.SoLoader;
importjava.util.Arrays;
importjava.util.List;
publicclassMainApplicationextendsApplicationimplementsReactApplication{
privatefinalReactNativeHostmReactNativeHost=newReactNativeHost(this){
@Override
publicbooleangetUseDeveloperSupport(){
returnBuildConfig.DEBUG;
}
@Override
protectedList<ReactPackage>getPackages(){
returnArrays.asList(
newMainReactPackage(),
newHelloPackage()
);
}
@Override
protectedStringgetJSMainModuleName(){
return"index";
}
};
@Override
publicReactNativeHostgetReactNativeHost(){
returnmReactNativeHost;
}
@Override
publicvoidonCreate(){
super.onCreate();
SoLoader.init(this,/*nativeexopackage*/false);
}
}
11. We'realldoneontheJavaportionofthisrecipe.WeneedtobuildourUI,
whichwillinvokethenativeHelloManagergreetUsermethodanddisplayits
output.Inthisexample,we'llbeusingTextInputandSwitchtoprovidename
andtheisAdminvalueforthenativemodulemethod.Thisisthesame
functionalityasweimplementedoniOSintheExposingcustomiOS
modulesrecipe.Let'sgettobuildingoutApp.js,startingwiththe
dependencieswe'llneed:
importReact,{Component}from'react';
import{
StyleSheet,
Text,
View,
NativeModules,
TextInput,
Switch,
DeviceEventEmitter
}from'react-native';
importButtonfrom'react-native-button';
12. WeneedtomakeareferencetotheHelloManagerobjectthatlivesonthe
importedNativeModulescomponent:
const{HelloManager}=NativeModules;
13. Let'screatetheAppclassandtheinitialstate:
exportdefaultclassAppextendsComponent{
state={
userName:null,
greetingMessage:null,
isAdmin:false
}
}
14. We'rereadytodefinethecomponent'srenderfunction.Thispieceofcode
willnotbedescribedingreatdetail,asit'sbasicallythesamerenderfunction
definedintheExposingcustomiOSmodulesrecipeatthebeginningofthis
chapter:
render(){
return(
<Viewstyle={styles.container}>
<Textstyle={styles.label}>
EnterUserName
</Text>
<TextInput
ref="userName"
autoCorrect={false}
style={styles.inputField}
placeholder="UserName"
onChangeText={(text)=>this.setState({userName:text})
}
/>
<Textstyle={styles.label}>
Admin
</Text>
<Switch
style={styles.radio}
onValueChange={
value=>this.setState({isAdmin:value})
}
value={this.state.isAdmin}
/>
<Button
disabled={!this.state.userName}
style={[
styles.buttonStyle,
!this.state.userName?styles.disabled:null
]}
onPress={this.greetUser}
>
Greet
</Button>
<Textstyle={styles.label}>
Response:
</Text>
<Textstyle={styles.message}>
{this.state.greetingMessage}
</Text>
</View>
);
}
15. WiththeUIrenderingthenecessarycomponents,wenowneedtowireup
theonPresshandlerofButtontomakethenativecallviaHelloManager.greetUser:
updateGreetingMessage=(result)=>{
this.setState({
greetingMessage:result
});
}
greetUser=()=>{
this.refs.userName.blur();
HelloManager.greetUser(
this.state.userName,
this.state.isAdmin,
this.updateGreetingMessage
);
}
16. We'lladdstylestolayoutandstyletheapp.Again,thesearethesamestyles
asusedintheExposingcustomiOSmodulesrecipeatthebeginningofthis
chapter:
conststyles=StyleSheet.create({
container:{
flex:1,
justifyContent:'center',
alignItems:'center',
backgroundColor:'#F5FCFF',
},
inputField:{
padding:20,
fontSize:30,
width:200
},
label:{
fontSize:18,
marginTop:18,
textAlign:'center',
},
radio:{
marginBottom:20
},
buttonStyle:{
padding:20,
backgroundColor:'#1DA1F2',
color:'#fff',
fontSize:18
},
message:{
fontSize:22,
marginLeft:50,
marginRight:50,
},
disabled:{
backgroundColor:'#3C3C3C'
}
});
17. Thefinalappshouldlooksimilartothefollowingscreenshot:
Howitworks...
Thisrecipecoversthefoundationformuchofwhatwe'llbedoingwithadding
nativeAndroidmodulesinfuturerecipes.Allnativemoduleclassesneedto
extendReactContextBaseJavaModule,implementtheconstructor,anddefinethegetName
method.AllmethodsthatshouldbeexposedtotheReactNativelayerneedto
havethe@ReactMethodannotation.CreatingaReactNativeAndroidnativemodule
hasmoreoverheadascomparedwithiOS,sinceyouhavetoalsowrapyour
moduleinaclassthatimplementsReactPackage(inthisrecipe,that'stheHelloPackage
module),andregisterthepackagewiththeReactNativeproject.Thisisdonein
steps7and8.
IntheJavaScriptportionoftherecipe,thegreetUserfunctionisexecutedwhenthe
userpressestheButtoncomponent.This,inturn,makesacallto
HelloManager.greetUser,passingalongtheuserNameandisAdminpropertiesfromstate
andtheupdateGreetingMessagemethodasacallback.TheupdateGreetingMessagesetsthe
newgreetingMessageonstate,causingarefreshoftheUIandthemessagetobe
displayed.
RenderingcustomAndroidview
components
OnereasonReactNativehasgainedsomuchpopularitysofarisitsabilityto
rendertrulynativeUIcomponents.WithnativeUIcomponentsonAndroid,
we'reabletoleveragenotonlytheGPUrenderingpower,butwealsogetthe
nativelookandfeelofnativecomponents,includingnativefonts,colors,and
animations.WebandhybridapplicationsonAndroiduseCSSpolyfillsto
simulateanativeanimationbut,inReactNative,wecangettherealthing.
We'llneedanewpureReactNativeappforthisrecipe.Let'sname
itNativeUIComponent.Inthisrecipe,we'lltakeanativeButtonandexposeitasa
ReactNativeviewcomponent.
Howtodoit...
1. Let'sstartbyopeningtheAndroidprojectinAndroidStudio.Inthe
AndroidStudiowelcomescreen,selectOpenanexistingAndroidStudio
projectandopentheandroiddirectoryoftheproject.
2. Opentheprojectexplorerandexpandthepackagestructureuntilyoucan
seetheJavasourcefiles(forexample,app/java/com.nativeuicomponent):
3. Right-clickonthepackageandselectNew|JavaClass.UseButtonViewManager
fortheclassnameandsettheKindfieldtoClass.
4. UsethesamemethodtoalsocreateaButtonPackageclass.
5. Let'sbeginimplementingourButtonViewManagerclass,whichmustbea
subclassofSimpleViewManager<View>.We'llstartwiththeimportsanddefinethe
classitself:
packagecom.nativeuicomponent;
importandroid.view.View;
importandroid.widget.Button;
importcom.facebook.react.bridge.Arguments;
importcom.facebook.react.bridge.ReactContext;
importcom.facebook.react.bridge.WritableMap;
importcom.facebook.react.uimanager.SimpleViewManager;
importcom.facebook.react.uimanager.ThemedReactContext;
importcom.facebook.react.uimanager.annotations.ReactProp;
importcom.facebook.react.uimanager.events.RCTEventEmitter;
publicclassButtonViewManagerextendsSimpleViewManager<Button>implementsView.OnClickListener{
//Definedonfollowingsteps
}
ThefileclassnameButtonViewManagerfollowstheAndroidnamingconventionofaddingthe
suffixViewManagertoanyViewcomponent.
6. Let'sstarttheclassdefinitionwiththegetNamemethodthatreturnsthestring
namewe'reassigningthecomponent,whichinthiscaseisButtonView:
publicclassButtonViewManagerextendsSimpleViewManager<Button>implementsView.OnClickListener{
@Override
publicStringgetName(){
return"ButtonView";
}
//Definedonfollowingsteps.
}
7. ThecreateViewInstancemethodisrequiredfordefininghowReactshould
initializethemodule:
@Override
protectedButtoncreateViewInstance(ThemedReactContextreactContext){
Buttonbutton=newButton(reactContext);
button.setOnClickListener(this);
returnbutton;
}
8. setButtonTextwillbeusedfromthepropertiesontheReactNativeelementto
setthetextonthebutton:
@ReactProp(name="buttonText")
publicvoidsetButtonText(Buttonbutton,StringbuttonText){
button.setText(buttonText);
}
9. TheonClickmethoddefineswhatwillhappenwhenthebuttonispressed.
ThismethodusesRCTEventEmittertohandlereceivingeventsfromtheReact
Nativelayer:
@Override
publicvoidonClick(Viewv){
WritableMapmap=Arguments.createMap();
ReactContextreactContext=(ReactContext)v.getContext();
reactContext.getJSModule(RCTEventEmitter.class).receiveEvent(v.getId(),"topChange",map);
}
10. Justlikeinthelastrecipe,weneedtoaddButtonViewManagertoButtonPackage;
however,thistime,we'redefiningitasViewManagerandnotNativeModule:
packagecom.nativeuicomponent;
importcom.facebook.react.ReactPackage;
importcom.facebook.react.bridge.NativeModule;
importcom.facebook.react.bridge.ReactApplicationContext;
importcom.facebook.react.uimanager.ViewManager;
importjava.util.Arrays;
importjava.util.Collections;
importjava.util.List;
publicclassButtonPackageimplementsReactPackage{
@Override
publicList<ViewManager>createViewManagers(ReactApplicationContextreactContext){
returnArrays.<ViewManager>asList(newButtonViewManager());
}
@Override
publicList<NativeModule>createNativeModules(ReactApplicationContextreactContext){
returnCollections.emptyList();
}
}
11. ThelaststepintheJavalayerisaddingButtonPackage
toMainApplication.MainApplication.javaalreadyhasquiteabitofboilerplate
codeinit,andwe'llonlyneedtochangethegetPackagesmethod:
@Override
protectedList<ReactPackage>getPackages(){
returnArrays.<ReactPackage>asList(
newMainReactPackage(),
newButtonPackage()
);
}
12. SwitchingovertotheJavaScriptlayer,let'sbuildoutourReactNativeapp.
First,let'screateanewButtoncomponentincomponents/Button.jsinthe
project'srootdirectory.Thisiswherethenativebuttonwillliveinsidethe
ReactNativelayeroftheapp.Therendermethodusesthenativebutton
asButtonView,whichwe'lldefineinthenextstep:
importReact,{Component}from'react';
import{requireNativeComponent,View}from'react-native';
exportdefaultclassButtonextendsComponent{
onChange=(event)=>{
if(this.properties.onTap){
this.properties.onTap(event.nativeEvent.message);
}
}
render(){
return(
<ButtonView
{...this.properties}
onChange={this.onChange}
/>
);
}
}
13. WecancreatethenativebuttonasaReactNativecomponentwith
therequireNativeComponenthelper,whichtakesthreeparameters:thestring
ButtonViewtodefinethecomponentsname,theButtoncomponentdefinedin
thepreviousstep,andtheoptionsobject.There'smoreinformationonthis
objectintheHowitworks...sectionattheendofthisrecipe:
constButtonView=requireNativeComponent(
'ButtonView',
Button,{
nativeOnly:{
onChange:true
}
}
);
14. We'rereadytodefinetheAppclass.Let'sstartwithdependencies,including
theButtoncomponentcreatedpreviously:
importReact,{Component}from'react';
import{
StyleSheet,
Text,
View
}from'react-native';
importButtonfrom'./components/Button';
15. TheAppcomponentinthisrecipeisessentiallythesameastheRendering
customiOSviewcomponentsrecipeearlierinthischapter.ThecustomonTap
propertyisfiredwhentheButtoncomponentispressed,adding1tothecount
propertyonstate:
exportdefaultclassAppextendsComponent{
state={
count:0
}
onButtonTap=()=>{
this.setState({
count:this.state.count+1
});
}
render(){
return(
<Viewstyle={styles.container}>
<ButtonbuttonText="PressMe!"
onTap={this.onButtonTap}
style={styles.button}
/>
<Text>
ButtonPressedCount:{this.state.count}
</Text>
</View>
);
}
}
16. Let'saddafewstylestolayoutandsizetheapp'sUI:
conststyles=StyleSheet.create({
container:{
flex:1,
justifyContent:'center',
alignItems:'center',
backgroundColor:'#F5FCFF',
},
button:{
height:40,
width:150
}
});
17. Thefinalappshouldlooksimilartothefollowingscreenshot:
Howitworks...
Whendefininganativeview,aswedidwiththeButtonViewManagerclass,itmust
extendSimpleViewManagerandrenderatypethatextendsView.Inourrecipe,we
renderedaButtonview,andweusethe@ReactPropannotationfordefining
properties.WhenweneedtocommunicatebacktotheJavaScriptlayer,wefire
aneventfromthenativecomponent,whichweimplementedinstep9ofthis
recipe.
Instep12,wecreatedanonChangelistener,whichwillexecutetheeventhandler
passedinfromtheAndroidlayer(event.nativeEvent.message).
RegardingtheuseofthenativeOnlyoptiononstep13,fromtheReactNative
documents:
Sometimesyou'llhavesomespecialpropertiesthatyouneedtoexposeforthenative
component,butdon'tactuallywantthemaspartoftheAPIfortheassociatedReact
component.Forexample,SwitchhasacustomonChangehandlerfortherawnativeevent,and
exposesanonValueChangehandlerpropertythatisinvokedwithjusttheBooleanvalue,rather
thantherawevent.Sinceyoudon'twantthesenativeonlypropertiestobepartoftheAPI,
youdon'twanttoputtheminpropTypes,butifyoudon't,you'llgetanerror.Thesolutionis
simplytocallthemoutviathenativeOnlyoption.
HandlingtheAndroidbackbutton
AnimportantfeaturethatallAndroiddeviceshaveisadedicatedbackbutton.
Bydefault,thebehaviorofthisbackbuttonistogototheprevious
runningactivity.SinceReactNativegenerallyonlyhasonemainReactActivity,
thiswilltakeyououtofyourapplication.Sometimes,wemaynotwanttohave
thisdefaultbehaviorandusethebackbuttoninsideourReactNative
application.
ThisrecipewillcreateasimpleexampleusingReactNative'slatestnavigation
implementation,NavigationExperimental,andattachabackbuttonlistenertostep
backthroughthenavigationstack.
Gettingready
Forthisrecipe,itmaybeagoodideatofamiliarizeyourselfwiththe
NavigationExperimentaldocumentation.We'vedemonstratedthisintheAdding
navigationtoyourapplicationrecipeinChapter1,SettingUpYourEnvironment.
Forthisrecipe,wecreatedaReactNativeapplicationtitledBackButton.
Howtodoit...
1. InordertomakeameaningfulexampleofusingtheAndroidbackbutton,
we'regoingtohavetoimplementNavigationExperimentalforourapplication.
Let'sstartoffbymakingaButtoncomponentthatwillbeusedtomanually
betweennavigationstates.Createafile,Button.js,inyourproject'sroot
directory(whereindex.android.jsislocated)andaddthefollowingcode:
importReact,{Component}from'react';
import{
StyleSheet,
Text,
View,
PixelRatio,
TouchableOpacity
}from'react-native';
exportdefaultclassButtonextendsComponent{
render(){
return(
<TouchableOpacity
style={styles.row}
onPress={this.properties.onPress}>
<Textstyle={styles.buttonText}>
{this.properties.text}
</Text>
</TouchableOpacity>
)
}
}
conststyles=StyleSheet.create({
row:{
padding:15,
backgroundColor:'white',
borderBottomWidth:1/PixelRatio.get(),
borderBottomColor:'#CDCDCD',
},
buttonText:{
fontSize:17,
fontWeight:'500',
}
});
2. Nowwe'regoingtoneedascenethatwillberenderedbytheNavigator.
ThisscenewillholdtheButtoncomponentwecreatedinthepreviousstep.
Createafile,InitialScene.js,andaddthefollowing:
importReact,{Component}from'react';
import{
StyleSheet,
Text,
View,
PixelRatio,
ScrollView
}from'react-native';
importButtonfrom'./Button';
exportdefaultclassInitialSceneextendsComponent{
render(){
return(
<ScrollViewstyle={styles.scrollView}>
<Textstyle={styles.row}>
Route:{this.properties.route.key}
</Text>
<Button
text="Next"
onPress={this.properties.onPushRoute}
/>
<Button
text="Back"
onPress={this.properties.onPopRoute}
/>
</ScrollView>
);
}
}
Youcanaddthefollowingstylesattheendofthefiletogivethe
scenesomestyling:
conststyles=StyleSheet.create({
navigator:{
flex:1,
},
scrollView:{
marginTop:64
},
row:{
padding:15,
backgroundColor:'white',
borderBottomWidth:1/PixelRatio.get(),
borderBottomColor:'#CDCDCD',
},
rowText:{
fontSize:17,
},
buttonText:{
fontSize:17,
fontWeight:'500',
},
});
3. ThenextstepistoimplementtheactualNavigatoritself.Thiswillcontain
theNavigationCardStack,instructionsonhowtorenderasceneand,most
importantlyinthiscase,ourbackbuttoneventhandler.Let'sgetrightinto
it;createaNavigator.jsfileandaddthefollowing:
importReact,{Component}from'react';
import{
StyleSheet,
Text,
View,
NavigationExperimental
}from'react-native';
importInitialScenefrom'./InitialScene';
const{
CardStack:NavigationCardStack
}=NavigationExperimental;
exportdefaultclassNavigatorextendsComponent{
constructor(properties,context){
super(properties,context);
this._onPushRoute=
this.properties.onNavigationChange.bind(null,'push');
this._onPopRoute=
this.properties.onNavigationChange.bind(null,'pop');
}
_renderScene=(sceneProps)=>{
return(
<InitialScene
route={sceneProps.scene.route}
onPushRoute={this._onPushRoute}
onPopRoute={this._onPopRoute}
/>
);
}
render(){
return(
<NavigationCardStack
onNavigationBack={this._onPopRoute}
navigationState={this.properties.navigationState}
renderScene={this._renderScene}
style={{flex:1}}
/>
);
}
}
Herewe'rerenderingtheInitialScenecomponentthatwecreatedand
puttingitonNavigationCardStack.
4. Nowit'stimetoaddsupportforthebackbutton.We'llbemodifyingour
Navigatorsinceitholdsthepropertythathandlesnavigationstatechange.
First,weneedtoimportBackAndroidfrom'react-native',soaddthefieldtothe
importblockafterNavigationExperimental.Attheendoftheconstructor,we're
goingtoaddtheeventlistenerforthebackbutton:
BackAndroid.addEventListener
('hardwareBackPress',this.onAndroidBackPress);
Lastly,weneedtoimplementthecallbackthatwepassedintotheevent
listener:
onAndroidBackPress=()=>{
if(this.properties.navigationState.index){
this._onPopRoute();
returntrue;
}
returnfalse;
}
5. Totiethisalltogether,weneedtorendertheNavigatorfromour
application'sentryclass.We'llstartbyimportingthenecessary
dependencies,openindex.android.js.
WeneedtoimportNavigationExperimentalfrom'react-native',ourNavigator
component,andgetareferencetoStateUtilsfromNavigationExperimental:
import{
AppRegistry,
StyleSheet,
Text,
View,
NavigationExperimental
}from'react-native';
const{
StateUtils:NavigationStateUtils
}=NavigationExperimental;
importNavigatorfrom'./Navigator';
6. Then,torendertheNavigator,overwritetheimplementationofthe
applicationclasswiththefollowing:
componentWillMount(){
this.state={
navigationState:{
index:0,
routes:[
{key:'Initial'}
]
}
};
}
_onNavigationChange=(type)=>{
letnavigationState=this.state.navigationState,
newNavigationState;
switch(type){
case'push':
newNavigationState=
NavigationStateUtils.push(navigationState,{
key:'Route-'+Date.now()
});
break;
case'pop':
newNavigationState=
NavigationStateUtils.pop(navigationState);
break;
}
if(newNavigationState!==navigationState){
this.setState({navigationState:newNavigationState});
}
}
render(){
return(
<Navigator
navigationState={this.state.navigationState}
onNavigationChange={this._onNavigationChange}
/>
);
}
Yourapplicationshouldlookasshowninthefollowingscreenshot,
andyoushouldbeabletousethebackbuttontonavigateupthe
stack:
Howitworks...
TheAndroidbackbuttonhandlerisquitetrivialtoimplement.Itshould,
however,beusefulinameaningfulwaythatfeelsnaturaltotheapplication.The
implementationoccursinstep4.Topreventthedefaultaction—inthiscase,it
wouldexitoutoftheapplication—wehavetoreturntruefromoureventlistener.
AddingNativeFunctionality-PartII
Inthischapter,wewillcoverthefollowingrecipes:
Reactingtochangesinapplicationstate
Copyingandpastingcontent
Receivingpushnotifications
AuthenticatingviatouchIDorfingerprintsensor
Hidingapplicationcontentwhenmultitasking
BackgroundprocessingoniOS
BackgroundprocessingonAndroid
PlayingaudiofilesoniOS
PlayingaudiofilesonAndroid
Introduction
Inthischapter,wewillcontinuewithmorerecipesthattouchondifferent
aspectsofwritingReactNativeappsthatinteractwithnativeiOSandAndroid
code.Wewillcoverexampleappsthatleveragebuilt-inandcommunitycreated
modules.Therecipescoverarangeoftopics,fromrenderingabasicbuttonto
creatingamultithreadedprocessthatdoesnotblockthemainapplication
threads.
Reactingtochangesinapplication
state
Theaveragemobiledeviceuserhasseveralappsthattheyuseonaregularbasis.
Ideally,alongwiththeothersocialmediaapps,games,mediaplayers,andmore,
userswillalsobeusingyourReactNativeapp.Anyspecificusermayspenda
shorttimeineachapplicationbecauseheorshemultitasks.Whatifwewanted
toreacttowhentheuserleavesourappandre-enters?Wecouldusethisasa
chancetosyncdatawiththeserver,ortotelltheuserthatwe'rehappytosee
themreturn,ortopolitelyaskforaratingontheappstore.
Thisrecipewillcoverthebasicsofreactingtochangesinthestateofthe
application,whichistosayreactingtowhentheappisintheforeground
(active),background,orinactive.
Forthisrecipe,let'screateanewpureReactNativeapptitledAppStateApp.
Howtodoit...
1. Fortunately,ReactNativeprovidessupportforlisteningtochangestothe
stateoftheappthroughtheAppStatemodule.Let'sbeginbuildingouttheapp
byaddingdependenciestotheApp.jsfile,asfollows:
importReact,{Component}from'react';
import{
AppState,
StyleSheet,
Text,
View
}from'react-native';
2. Intherecipe,we'regoingtokeeptrackofthepreviousstatetoseewhere
theusercamefrom.Ifit'stheirfirsttimeenteringtheapp,wewillwelcome
them,andifthey'rereturning,wewillwelcomethembackinstead.Todo
so,weneedtokeepareferencetothepreviousandcurrentappstates.We'll
useinstancevariablespreviousAppStateandcurrentAppStatesinsteadofusing
stateforthispurpose,simplytoavoidpotentialnamingconfusion.We'll
usestatetoholdthestatusmessagetotheuser,asfollows:
exportdefaultclassAppextendsComponent{
previousAppState=null;
currentAppState=null;
state={
appStatusMessage='Welcome!'
}
//Definedonfollowingsteps
}
3. Whenthecomponentmounts,we'llusetheAppStatecomponenttoaddan
eventlistenertothechangeevent.Whenevertheapp'sstatechanges(for
example,whentheappisbackgrounded),thechangeeventwillbefired,
whereuponwe'llfireourhandleAppStateChangehandler,definedinthenext
step,asfollows:
componentWillMount(){
AppState.addEventListener('change',this.handleAppStateChange);
}
4. ThehandleAppStateChangemethodwillreceivetheappStateasaparameter,
whichwecanexpecttobeoneofthreestrings:inactiveiftheappis
unloadedfrommemory,backgroundiftheappisinmemoryanbackgrounded,
andactiveiftheappisforegrounded.We'lluseaswitchstatementtoupdate
thestatusMessageonstateaccordingly:
handleAppStateChange=(appState)=>{
letstatusMessage;
this.previousAppState=this.currentAppState;
this.currentAppState=appState;
switch(appState){
case'inactive':
statusMessage="GoodBye.";
break;
case'background':
statusMessage="AppIsHidden...";
break;
case'active':
statusMessage='WelcomeBack!'
break;
}
this.setState({statusMessage});
}
5. Therendermethodisverybasicinthisrecipe,sinceitonlyneedstodisplay
thestatusmessagetotheuser,asfollows:
render(){
return(
<Viewstyle={styles.container}>
<Textstyle={styles.welcome}>
{this.state.appStatus}
</Text>
</View>
);
}
6. Thestylesforthisapparesimilarlybasic,addingfontsize,color,and
margin,asfollows:
conststyles=StyleSheet.create({
container:{
flex:1,
justifyContent:'center',
alignItems:'center',
backgroundColor:'#fff',
},
welcome:{
fontSize:40,
textAlign:'center',
margin:10,
},
instructions:{
textAlign:'center',
color:'#333333',
marginBottom:5,
},
});
7. Thecompletedappshouldnowdisplaytheappropriatestatusmessage
dependingonthestateoftheapponagivendevice.
Howitworks...
Inthisrecipe,wemadeuseofthebuilt-inAppStatemodule.Themodulelistensto
theActivityeventsonAndroid,andoniOSitusesNSNotificationCentertoregistera
listeneronvariousUIApplicationevents.Notethatbothplatformssupport
theactiveandbackgroundstates;however,theinactivestateisaniOSonlyconcept.
Androiddoesnotexplicitlysupporttheinactivestateduetoitsmultitasking
implementation,soonlytogglesappsbetweenbackgroundandactivestates.To
achievetheequivalentoftheiOSinactivestateonAndroid,seetheHiding
applicationcontentwhenmultitaskingrecipelaterinthischapter.
Copyingandpastingcontent
Oneofthemostusedfeaturesinbothdesktopandmobileoperatingsystemsis
theclipboardforcopyingandpastingcontent.Acommonscenarioonmobileis
fillingformswithlengthytext,suchaslongemailaddressesor
passwords.Insteadoftypingitwithafewtypos,itwouldbeeasiertojustopen
yourcontactsapplicationandcopytheemailfromthereandpasteitinto
yourTextInputfield.
ThisrecipewillshowabasicexampleonbothAndroidandiOSofhowwecan
copyandpastetextinsideourReactNativeapplication.Inoursampleapp,we
willhavebothastaticTextviewandaTextInputfieldthatyoucancopyits
contentstotheclipboard.Also,therewillbeabuttonthatoutputsthecontentsof
theclipboardtotheview.
Gettingready
Forthisrecipe,wecreatedaReactNativeapplicationtitledCopyPasteApp.
Inthisrecipe,wewillbeusingreact-native-buttonagain.Installitwithnpm:
npminstallreact-native-button
Alternatively,wecanuseyarn:
yarnaddreact-native-button
Howtodoit...
1. Let'sstartoffbycreatingaClipboardTextcomponentthatbothuses
aTextcomponenttodisplaytextandprovidestheabilitytocopyitscontents
totheclipboardvialongpress.Let'screateacomponentfolderintherootof
theproject,andaClipboardText.jsfileinsideofit.We'llstartbyimporting
dependencies,asfollows:
importReact,{Component}from'react';
import{
StyleSheet,
Text,
View,
Clipboard,
TextInput
}from'react-native';
importButtonfrom'react-native-button';
2. Nextwe'lldefinetheAppclassandtheinitialstate.Wewilluse
theclipboardContentpropertyonstateforstoringtextbeingpastedfromthe
clipboardintotheUI,asfollows:
exportdefaultclassAppextendsComponent{
state={
clipboardContent:null
}
//Definedinfollowingsteps
}
3. TheUIwillhaveoneTextcomponentwhosetextwillbycopyablevialong
press.Let'sdefinethecopyToClipboardmethod.We'llgrabtheinputvia
itsref(whichwe'lldefinelater),andaccessthecomponent'stextvia
itsprops.childrenproperty.Oncethetexthasbeenstoredinalocalvariable,
wesimplypassittosetStringmethodofClipboardtocopythetexttothe
clipboard,asfollows:
copyToClipboard=()=>{
constsourceText=this.refs.sourceText.props.children;
Clipboard.setString(sourceText);
}
4. Similarly,we'llalsoneedamethodthatwillpastetextintotheapp'sUI
fromtheclipboard.ThismethodwillusetheClipboard'sgetStringmethod,
andsavethereturnedstringtoclipboardContentpropertyofstate,re-
renderingtheapp'sUItoreflectthepastedtext,asfollows:
getClipboardContent=async()=>{
constclipboardContent=awaitClipboard.getString();
this.setState({
clipboardContent
});
}
5. Therendermethodwillbemadeupoftwosections:thefirstismadeof
thingstocopy,andthesecondisawayforpastingtextfromtheclipboard
intotheUI.Let'sstartwiththefirstsection,whichconsistsofaTextinput
whoseonLongPresspropiswiredtothecopyToClipboardmethodwecreatedin
step3,andatextinputfornormalnativecopy/pasting:
render(){
return(
<Viewstyle={styles.container}>
<Textstyle={styles.instructions}>
TapandHoldthenextlinetocopyittotheClipboard:
</Text>
<Text
ref="sourceText"
onLongPress={this.copyToClipboard}
>
ReactNativeCookbook
</Text>
<Textstyle={styles.instructions}>
InputsometextintotheTextInputbelowandCut/Copyas
younormallywould:
</Text>
<TextInputstyle={styles.textInput}/>
//Definedonnextstep
</View>
);
}
6. ThesecondportionoftheUIconsistsofaTextcomponentfordisplayingthe
currentvaluesavedinclipboardContentonstate,andabuttonthatwillpaste
fromtheclipboardusingthegetClipboardContentmethodwedefinedinstep4:
render(){
return(
<Viewstyle={styles.container}>
//Definedinpreviousstep
<Viewstyle={styles.row}>
<Textstyle={styles.rowText}>
ClipboardContents:
</Text>
</View>
<Viewstyle={styles.row}>
<Textstyle={styles.content}>
{this.state.clipboardContent}
</Text>
</View>
<Button
containerStyle={styles.buttonContainer}
style={styles.buttonStyle}
onPress={this.getClipboardContent}
>
PasteClipboard
</Button>
</View>
);
}
Theoutputisasshowninfollowingscreenshot:
Howitworks...
Inthisrecipe,webuiltasimplecopyandpasteapplicationbyusing
theClipboardAPIprovidedbyReactNative.TheClipboardmodulecurrentlyonly
supportscontentoftypeString,eventhoughthedevicescancopymore
complicateddata.Thismodulemakesusingtheclipboardaseasyascallingthe
methodssetStringandgetString.
Receivingpushnotifications
Inourever-connectedmobileworld,itisimportantforourappstoencourage
andenticeustoopenthem.Theyshouldcommunicatethatthereissomepiece
ofinformationwaitingforusifweopentheapp,thatwemustbeawareof
immediately.Thisiswheretheconceptofpushnotificationscomesin.Push
notificationshaveexistedsince2009oniOSand2010forAndroid,andare
frequentlyusedbyapplicationstolettheuserknowofachangethattheyshould
reactto.Thiscouldbeatimedurationprocessthathasfinished,oramessage
hasbeenposteddirectedattheuser;theusecasesareendless.
ThisrecipefocusessolelyonworkingwithpushnotificationsonbothiOSand
Android.WearegoingtoregisterourappwithAppleAPNSandGoogleCloud
Messaging(GCM)andsuccessfullyreceivepushnotifications.
Gettingready
Thisrecipeassumesyouhaveaworkingpushnotificationserverthat
communicateswithAPNSandGCM.Intherecipe,wewilloutputthedevice
tokenforyoutoregisterwithyourserver.
Forthisrecipe,wecreatedaReactNativeapplicationtitledPushNotifications.
Inthisrecipe,wewillusethereact-native-push-notificationslibrary.Toinstallit,
runthefollowingcommandintheterminalfromyourprojectrootdirectory:
$npminstallreact-native-push-notification--save
$react-nativelink
PushnotificationsforiOSrequireaphysicaldevice;youcannottestthisonasimulator.For
Android,yourAVDmusthaveGoogleAPIsinstalled.
Howtodoit...
1. BeforewebeginwritingReactNativecodetoreceivenotifications,we
havetoaddthepushnotificationsentitlementtoourAppID.OpentheiOS
ProjectinXcode.Theprojectfileislocatedintheios/directoryofyour
ReactNativeapplication(for
example,./PushNotifications/ios/PushNotifications.xcodeproj):
2. Beforeproceedingwiththeentitlementconfiguration,makesureyou
changeyourBundleIdentifiertobemeaningful.SelectthePROJECTinthe
projectnavigatorsidebar,thenselecttheapplicationtarget.UnderBundle
Identifier,changeittobemoremeaningful.Generallytheformat
iscom(org|net|...).companyname.appname:
3. PleaserefertothefollowingURLforaddingthepushnotification
entitlementandgeneratingacertificateforyourserver:
https://developer.apple.com/library/ios/documentation/IDEs/Conceptual/AppDistribut
ionGuide/AddingCapabilities/AddingCapabilities.html#//apple_ref/doc/uid/TP40012582
-CH26-SW6
4. NowthatourReactNativeappisreadytoreceiveApplepushnotifications,
let'swritesomecodetodoso.Openupyourindex.ios.jsfileandaddthe
followingimport:
importPushNotificationfrom'react-native-push-notification';
5. Next,weneedtoregisterforpushnotificationsand,inoursampleapp,we
willdisplaythemessageontheview,asfollows:
componentWillMount(){
this.setState({
notification:undefined
});
PushNotification.configure({
onRegister:function(token){
console.log('register',token);
},
onNotification:this.onNotificationReceived
});
}
onNotificationReceived=(notification)=>{
this.setState({
notification:notification.message
});
}
6. Now,ifyouwanttodisplaythenotificationmessagewhenitisreceived,
justaddthefollowingtoyourrendermethod:
<Text>{this.state.notification}</Text>
Thisishowoursampleapplooksrunningonadevice.
7. It'stimetomoveontoAndroid.Thispartisabitmoreinvolvedasthereis
currentlynoout-of-the-boxsupportfromReactNative.
8. FollowtheCreateanAPIprojecttogetaconfigurationfileandaddthe
configurationfiletoyourproject,ignoringtheGradleinstructions,from:
https://developers.google.com/cloud-messaging/android/client
BemakesuretostoreyourserverAPIkeyandsenderIDsomewherefor
easyretrieval.
9. GotothefollowingURLandfollowtheinstructionsonAndroid
installation:
https://github.com/zo0r/react-native-push-notification/blob/master/README.md#andro
id-installation
10. OnefeaturethatwemaywanttoimplementonAndroidthatalreadyexists
oniOSistoopenupourappwhentheusertapsanotification.
OpenAndroidManifest.xml(it'slocatedin./app/src/main)andaddthefollowing
toourMainActivitydefinition:
<intent-filter>
<actionandroid:name="OPEN_ACTIVITY_1"/>
<categoryandroid:name="android.intent.category.DEFAULT"/>
</intent-filter>
11. Whenyousendnotificationsfromyourserver,youhavetoensurethatyou
settheclick_actionto'OPEN_ACTIVITY_1',asfollows:
message.addNotification({
title:'Notification',
body:'ThanksforreadingReactNativeCookbook!',
icon:'ic_launcher',
click_action:'OPEN_ACTIVITY_1'
});
12. Nowthatwe'realldonesettingupourAndroidproject,wecanworkonthe
UI.Repeatstep4tostep6forindex.android.js.
13. ToregisterwiththeGCMservice,wemustmaketwochangesto
ourPushNotification.configuremethod.Addthefollowingtwoproperties:
senderID:'<SenderIDFromGoogle>',
requestPermissions:true
NowyoushouldbeabletosuccessfullyreceivepushnotificationsonAndroid
andopenyourapplicationwhenyoutapthenotification.
Howitworks...
Pushnotificationsareoneofthemoreadvancedrecipesthatwewillbecovering
inthischapter.Thelevelofdifficultyappliesmoretosettingupour
environments,ratherthanreactingtotheminourReactNativeapplication.Step
3andstep8focusonsettingupourdevelopmentservers.
OneimportantdistinctionbetweeniOSandAndroidistheactionthatoccurs
whentheusertapsonanotification.iOSdefaultstoopentheapplication,so,to
replicatethisfeature,weneededtoaddanintent-filter,asseeninstep11and
step12.Ifyoudonotwanttoauto-launchyourapplication,thenskipstep11and
donotsendtheclick_actioninyournotificationpayload.
AuthenticatingviatouchIDor
fingerprintsensor
Securityisaparamountconcerninsoftware,especiallywhenthereisanysortof
authentication.Breachesandleakedpasswordshavebecomeapartofthedaily
newscycle,andcompaniesofallsizesarewisinguptotheneedfor
implementingaddedsecuritymeasuresintheirapps.Onesuchmeasurein
mobiledevicesisbiometricauthentication,whichusesfingerprintscanningor
facerecognitiontechnologytoprovidesupplementaryidentificationmethods.
Thisrecipecovershowtoaddfingerprintscanningandfacerecognitionsecurity.
Thankstothereact-native-touch-idlibrary,thisprocesshasbeensimplifiedand
streamlinedinReactNativeappdevelopment.
Gettingready
Forthisrecipewe'llneedanewpureReactNativeapp.Let'scallitBiometricAuth.
We'llbeusingthereact-native-buttonandreact-native-touch-idlibraries.Installthem
withnpm:
npminstallreact-native-buttonreact-native-touch-id--save
Alternatively,wecanuseyarn:
yarnaddreact-native-buttonreact-native-touch-id
Onceinstalled,react-native-touch-idwillneedtobelinked,sobesuretofollowup
with:
react-nativelink
Permissionswillalsoneedtobeadjustedmanually.ForAndroidpermissions,
locatetheAndroidManifest.xmlfileintheproject,whichshouldbe
atHiddenContentApp/android/app/src/main/AndroidManifest.xml.Alongwiththeother
permissionsinthisfile,you'llneedtoaddthefollowing:
<uses-permissionandroid:name="android.permission.USE_FINGERPRINT"/>
ForiOSpermissions,you'llneedtoupdatetheInfo.plistfileinatexteditor.
Alongwithalltheotherentries,addthefollowing:
<key>NSFaceIDUsageDescription</key>
<string>EnablingFaceIDallowsyouquickandsecureaccesstoyouraccount.</string>
Howtodoit...
1. Let'sstartbyaddingdependenciestotheApp.jsfile,asfollows:
importReact,{Component}from'react';
import{
StyleSheet,
Text,
View
}from'react-native';
importButtonfrom'react-native-button';
importTouchIDfrom'react-native-touch-id';
2. Nextwe'lldefinethatAppclassandtheinitialstate.We'llkeeptrackofthe
authenticationstatusontheauthStatuspropertyofstate,asfollows:
exportdefaultclassAppextendsComponent{
state={
authStatus:null
}
//Definedinfollowingsteps
}
3. Let'sdefinetheauthenticatemethod,whichwillbefiredonbuttonpress,and
willinitiateauthenticationonthedevice.Wecaninitiateauthenticationby
executingtheTouchIDcomponent'sauthenticatemethod.Thismethod'sfirst
parameterisanoptionalstringexplainingthereasonfortherequest,as
follows:
authenticate=()=>{
TouchID.authenticate('Accesssecretinformation!')
.then(this.handleAuthSuccess)
.catch(this.handleAuthFailure);
}
4. ThismethodfiresthehandleAuthSuccessmethodonsuccess.Let'sdefineit
now.ThismethodsimplyupdatestheauthStatuspropertyofstatetothe
stringAuthenticated,asfollows:
handleAuthSuccess=()=>{
this.setState({
authStatus:'Authenticated'
});
}
5. Similarly,ifauthenticationfails,thehandleAuthFailurefunctionwillbecalled,
whichwillupdatethesamestate.authStatustothestringNotAuthenticated,as
follows:
handleAuthFailure=()=>{
this.setState({
authStatus:'NotAuthenticated'
});
}
6. Therendermethodwillneedabuttontoinitiatetheauthenticationrequest,
andtwoTextcomponents:oneforalabel,andonetodisplaythe
authenticationstatus,asfollows:
render(){
return(
<Viewstyle={styles.container}>
<Button
containerStyle={styles.buttonContainer}
style={styles.button}
onPress={this.authenticate}>
Authenticate
</Button>
<Textstyle={styles.label}>AuthenticationStatus</Text>
<Textstyle={styles.welcome}>{this.state.authStatus}</Text>
</View>
);
}
7. Finally,we'lladdstylestocolor,size,andlayouttheUI,asfollows:
conststyles=StyleSheet.create({
container:{
flex:1,
justifyContent:'center',
alignItems:'center',
backgroundColor:'#fff',
},
welcome:{
fontSize:20,
textAlign:'center',
margin:10,
},
label:{
textAlign:'center',
color:'#333333',
marginBottom:5,
},
buttonContainer:{
width:150,
padding:10,
margin:5,
height:40,
overflow:'hidden',
backgroundColor:'#FF5722'
},
button:{
fontSize:16,
color:'white'
}
});
Howitworks...
Thisrecipehasillustratedhowsimpleitistoincorporatenativefingerprintand
facialrecognitionsecurityintoaReactNativeapp.Thecall
toTouchID.authenticatealsotakesasecond,optionaloptionsobjectparameterwith
threeproperties:titleforthetitleoftheconfirmationdialog(Android
only),colorforthecolorofthedialog(Androidonly),andafallbackLabelfor
editingthedefaultShowPasswordlabel(iOSonly).
Hidingapplicationcontentwhen
multitasking
Keepingthethemeofapplicationsecuritygoing,wehavetobewarysometimes
ofunwantedeyesandhandstouchingourdevicesandpotentiallygettingaccess
toourapplications.Inordertoprotecttheuserfrompryingeyeswhilelookingat
sensitiveinformation,wecanmaskourapplicationwhentheapplicationis
hidden,butstillactive.Oncetheuserreturnstotheapplication,wewouldsimply
removethemaskandtheusercancontinueusingtheappasnormal.Agood
examplewouldbetheChasebankingapplication;ithidesyoursensitivebanking
informationwhentheapplicationisnotintheforeground.
Thisrecipewillshowyouhowtorenderanimagetomaskyourapplicationand
removeitoncetheapplicationreturnstotheforegroundoractivestate.Wewill
coverbothiOSandAndroid;however,theimplementationvariesinitsentirety.
ForiOS,weemployapureObjective-Cimplementationforoptimal
performance.ForAndroid,we'regoingtohavetomakesomemodificationsto
theMainActivityinordertosendaneventtoourJavaScriptlayerthatthe
applicationhaslostfocus.Wewillhandletherenderingoftheimagemaskthere.
Gettingready
We'regoingtoneedanimagehandytouseasthemaskwhentheappisnot
foregrounded.IchosetouseaniPhonewallpaper,whichyoucanfindat:
http://www.hdiphone7wallpapers.com/2016/09/white-squares-iphone-7-and-7-plus-wallpapers.ht
ml
Theimageisasortofstylizedmosaicpattern.Itlookslikethis.
Youcanofcourseusewhateverimageyou'dlike.Inthisrecipe,theimagefile
willbenamedhidden.jpg,sorenameyourimageaccordingly.
We'llneedanewpureReactNativeapp.Let'scallitHiddenContentApp.
Howtodoit...
1. Let'sbeginbyaddingthemaskimagetotheiOSportionoftheapp.We'll
needtoopentheiosfolderoftheprojectinXcode,locatedin
theios/directoryofthenewReactNativeapp.
2. Wecanaddthehidden.jpgimagetotheprojectbydragginganddroppingthe
imageintotheImages.xcassetsfolderoftheprojectinXcode,asshowninthis
screenshot.
3. Nextwe'lladdanewimplementationandtwomethodsto
theAppDelegate.mfile.Theentiretyofthefilecanbefoundasfollows,
includinggeneratedcode.Thecodewe'readdingismarkedinboldfor
clarity.We'reextendingtheapplicationWillResignActivemethod,whichwillfire
wheneveragivenappchangesfrombeingforegrounded,toadd
animageViewwiththehidden.jpgasitsimage.Similarly,wealsoneedtoextend
theoppositemethod,applicationDidBecomeActive,toremovetheimagewhen
theappisre-foregrounded:
#import"AppDelegate.h"
#import<React/RCTBundleURLProvider.h>
#import<React/RCTRootView.h>
@implementationAppDelegate{
UIImageView*imageView;
}
-(BOOL)application:(UIApplication*)applicationdidFinishLaunchingWithOptions:(NSDictionary*)launchOptions
{
NSURL*jsCodeLocation;
jsCodeLocation=[[RCTBundleURLProvidersharedSettings]jsBundleURLForBundleRoot:@"index"fallbackResource:nil];
RCTRootView*rootView=[[RCTRootViewalloc]initWithBundleURL:jsCodeLocation
moduleName:@"HiddenContentApp"
initialProperties:nil
launchOptions:launchOptions];
rootView.backgroundColor=[[UIColoralloc]initWithRed:1.0fgreen:1.0fblue:1.0falpha:1];
self.window=[[UIWindowalloc]initWithFrame:[UIScreenmainScreen].bounds];
UIViewController*rootViewController=[UIViewControllernew];
rootViewController.view=rootView;
self.window.rootViewController=rootViewController;
[self.windowmakeKeyAndVisible];
returnYES;
}
-(void)applicationWillResignActive:(UIApplication*)application{
imageView=[[UIImageViewalloc]initWithFrame:[self.windowframe]];
[imageViewsetImage:[UIImageimageNamed:@"hidden.jpg"]];
[self.windowaddSubview:imageView];
}
-(void)applicationDidBecomeActive:(UIApplication*)application{
if(imageView!=nil){
[imageViewremoveFromSuperview];
imageView=nil;
}
}
@end
4. Withthepreviousthreesteps,alloftheworkrequiredfordisplayingthe
maskintheiOSappiscomplete.Let'smoveontotheAndroidportionby
openingtheAndroidportionoftheprojectinAndroidStudio.InAndroid
Studio,selectOpenanexistingAndroidStudioprojectandopen
theandroiddirectoryoftheproject.
5. Theonlynativecodewe'llneedtoupdateintheAndroidprojectlives
inMainActivity.java,locatedhere:
We'llneedtoaddonemethod,aswellasthethreeimportsfromReactthe
methoduses.Again,thecompleteMainActivity.javafileisbelow,withaddedcode
markedinbold.We'redefininganonWindowFocusChangedmethodthatextendsthe
basemethod'sfunctionality.ThebaseonWindowFocusChangedAndroidmethodisfired
wheneveragivenapp'sfocushaschanged,passingwithitahasFocusBoolean
representingwhethertheapphasfocusornot.Ourextensionwilleffectively
passthathasFocusBooleanfromtheparentmethoddowntotheReactNativelayer
viaaneventwe'renamingfocusChange,asfollows:
packagecom.hiddencontentapp;
importcom.facebook.react.ReactActivity;
importcom.facebook.react.bridge.Arguments;
importcom.facebook.react.bridge.WritableMap;
importcom.facebook.react.modules.core.DeviceEventManagerModule;
publicclassMainActivityextendsReactActivity{
/**
*ReturnsthenameofthemaincomponentregisteredfromJavaScript.
*Thisisusedtoschedulerenderingofthecomponent.
*/
@Override
protectedStringgetMainComponentName(){
return"HiddenContentApp";
}
@Override
publicvoidonWindowFocusChanged(booleanhasFocus){
super.onWindowFocusChanged(hasFocus);
if(getReactNativeHost().getReactInstanceManager().getCurrentReactContext()!=null){
WritableMapparams=Arguments.createMap();
params.putBoolean("appHasFocus",hasFocus);
getReactNativeHost().getReactInstanceManager()
.getCurrentReactContext()
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit("focusChange",params);
}
}
}
6. Tousethehidden.jpgmaskimageinAndroid,we'llneedtoalsoadditto
theReactNativeproject.Let'screateanewassetsfolderintherootofthe
ReactNativeproject,andaddthehidden.jpgimagefiletothenewfolder.
7. Withthenativepiecesinplace,we'rereadytoturntotheJavaScriptportion
oftheapp.Let'saddtheimportswe'llbeusingtoApp.js,asfollows:
importReact,{Component}from'react';
import{
StyleSheet,
Text,
View,
DeviceEventEmitter,
Image
}from'react-native';
8. Next,let'screatetheAppclassandtheinitialstate.Thestatewillonlyneed
ashowMaskBoolean,whichwilldictateifthemaskshouldbedisplayed,as
follows:
exportdefaultclassAppextendsComponent{
state={
showMask:null
}
//Definedinfollowingsteps
}
9. Whenthecomponentmounts,wewanttoregisteraneventlistenertolisten
toeventsemittedfromthenativeAndroidlayerusing
theDeviceEventEmitter'saddListenermethod,passingthestringfocusChangeasthe
nameoftheeventtolistenforasthefirstparameter,andacallbackto
executeasthesecondparameter.Asyoumayrecall,focusChangeisthename
weassignedtheeventinMainActivity.javaintheonWindowFocusChangemethod
instep5.Registertheeventlistenerasfollows:
componentWillMount(){
this.subscription=DeviceEventEmitter.addListener(
'focusChange',
this.onFocusChange
);
}
10. Inthisstepwesavedtheeventlistenertotheclassmemberthis.subscription.
Thiswillallowfortheeventlistenertobecleaneduponcethecomponent
isunmounted.Weachievethisbysimplycallingtheremovemethod
onthis.subscriptionwhenthecomponentunmounts,via
thecomponentWillUnmountlifecyclehook,asfollows:
componentWillUnmount(){
this.subscription.remove();
}
11. Let'sdefinetheonFocusChangehandlerusedinstep9.Themethodreceives
aparamsobjectwithanappHasFocusBooleanthat'sbeenpassedfromthenative
layerviatheonWindowFocusChangedmethoddefinedinstep5.Bysetting
theshowMaskBooleanonstatetotheinverseoftheappHasFocusBoolean,we
canusethatintherenderfunctiontotoggledisplayingthehidden.jpgimage,
asfollows:
onFocusChange=(params)=>{
this.setState({showMask:!params.appHasFocus})
}
12. Therendermethod'smaincontentisnotimportantinthisrecipe,butwecan
useittoapplythehidden.jpgmaskimagewhentheshowMaskpropertyonstate
istrue,asfollows:
render(){
if(this.state.showMask){
return(<Imagesource={require('./assets/hidden.jpg')}/>);
}
return(
<Viewstyle={styles.container}>
<Textstyle={styles.welcome}>WelcometoReactNative!</Text>
</View>
);
}
13. Theappiscomplete.Oncetheappisloaded,youshouldbeabletogotothe
appselectionview(doublepressinghomeoniOS,orthesquarebuttonon
Android)andseethemaskimageappliedtotheappwhenitisnot
foregrounded.NotethatAndroidemulatorsmaynotproperlyapplythe
maskasexpected,sothisfeaturemightrequireanAndroiddevicefor
testing.
Howitworks...
Inthisrecipewe'veseenanexampleofhavingtousetwoseparateapproaches
foraccomplishingthesametask.ForiOS,wehandleddisplayingtheimage
maskexclusivelyinthenativelayer,withoutanyneedfortheReactNativelayer.
ForAndroid,weusedReactNativetohandletheimagemasking.
Instep3weextendedtwoObjective-Cmethods:applicationWillResignActive,which
fireswhenanappchangesfrombeingforegrounded,
andapplicationDidBecomeActive,whichfireswhentheappisforegrounded.Foreach
event,wesimplytoggleanimageViewthatdisplaysthehidden.jpgimagestorein
theImages.xcassettesfolderintheXcodeproject.
Instep5weusedtheReactclassRCTDeviceEventEmitterfrom
theDeviceEventManagerModuletoemitaneventnamedfocusChange,passingalong
aparamsobjectwiththeappHasFocusbooleantotheReactNativelayer,asfollows:
getReactNativeHost().getReactInstanceManager()
.getCurrentReactContext()
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit("focusChange",params);
}
Instep9wedefinedthecomponentWillMountlifecyclehook,whichsetsupanevent
listenerforthisfocusChangeeventthatwillbeemittedfromthenativeAndroid
layer,firingtheonFocusChangemethod,whichwillupdatethevalue
ofstate'sshowMaskvaluebasedonthenativeappHasFocusvalue,triggeringarerender,
displayingthemaskasappropriate.
BackgroundprocessingoniOS
Overthelastseveralyears,processingpowerinmobiledeviceshasincreased
considerably.Usersaredemandingricherexperiencesandonemethodof
achievingimprovedperformanceonmodernmobiledevicesisvia
multithreading.Mostmobiledevicestodayarepoweredbymulticore
processors,andtheiroperatingsystemsnowofferdeveloperseasyabstractions
forexecutingcodeinthebackground,withoutinterferingwiththeperformance
oftheapp'sUI.
ThisrecipewillcoverboththeuseofiOS'sGrandCentralDispatch(GCD)to
executeasynchronousbackgroundprocessingonanewthread,and
communicatingbacktotheReactNativelayerwhentheprocessingiscomplete.
Gettingready
Forthisrecipe,we'llneedanewpureReactNativeapplication.Let'sname
itMultiThreadingApp.
We'llalsobeusingthereact-native-buttonlibrary.Installitwithnpm:
npminstallreact-native-button--save
Alternatively,wecanuseyarn:
yarnaddreact-native-button--save
Howtodoit...
1. We'llstartbyopeningtheiOSProjectinXcode,locatedintheiosdirectory
ofthenewReactNativeapp.
2. Let'saddanewCocoaclassfilenamedBackgroundTaskManagerof
subclassNSObject.RefertotheExposingCustomiOSModulesrecipeinthis
chapterformoredetailsondoingthisinXcode.
3. Next,letswirethenewmoduletotheReactRCTBrideModuleinthenew
module'sheaderfile,BackgroundTaskManager.h.Thecodetobeaddedismarked
inboldinthefollowingsnippet:
#import<Foundation/Foundation.h>
#import<dispatch/dispatch.h>
#import"RCTBridgeModule.h"
@interfaceBackgroundTaskManager:NSObject<RCTBridgeModule>{
dispatch_queue_tbackgroundQueue;
}
@end
4. We'llimplementthenativemoduleintheBackgroundTaskManager.mfile.Again,
thenewcodewe'readdingismarkedinboldinthefollowingsnippet:
#import"BackgroundTaskManager.h"
#import"RCTBridge.h"
#import"RCTEventDispatcher.h"
@implementationBackgroundTaskManager
@synthesizebridge=_bridge;
RCT_EXPORT_MODULE();
RCT_EXPORT_METHOD(loadInBackground){
backgroundQueue=dispatch_queue_create("com.moduscreate.bgqueue",NULL);

dispatch_async(backgroundQueue,^{
NSLog(@"processingbackground");
[self.bridge.eventDispatchersendAppEventWithName:@"backgroundProgress"body:@{@"status":@"Loading"}];
[NSThreadsleepForTimeInterval:5];
NSLog(@"slept");
dispatch_async(dispatch_get_main_queue(),^{
NSLog(@"Doneprocessing;mainthread");
[self.bridge.eventDispatchersendAppEventWithName:@"backgroundProgress"body:@{@"status":@"Done"}];
});
});
}
@end
5. Let'sturntotheJavaScriptlayernext.We'llstartbyaddingdependenciesto
theApp.jsfile.Aspartofthedependencies,wewillalsoneedtoimport
theBackgroundTaskManagernativemodulethatwedefinedinstep3andstep4,
asfollows:
importReact,{Component}from'react';
import{
StyleSheet,
Text,
View,
NativeModules,
NativeAppEventEmitter
}from'react-native';
importButtonfrom'react-native-button';
constBackgroundTaskManager=NativeModules.BackgroundTaskManager;
6. Let'sdefinetheAppclass,withaninitialstateofbackgroundTaskStatussettothe
stringNotStarted,andadoNothingCountpropertyinitializedto0,asfollows:
exportdefaultclassAppextendsComponent{
state={
backgroundTaskStatus:'NotStarted',
counter:0
}
//Definedinfollowingsteps
}
7. We'llneedtolistentothebackgroundProcesseventthatwillbeemittedfrom
thenativeiOSlayerfromthecustommodulewecreatedinstep3andstep
4.Let'ssetupaneventlistenerusingtheNativeAppEventEmitterReactNative
component,whichsetsthebackgroundTaskStatuspropertyofstatetothevalue
ofstatusontheeventobjectreceivedfromthenativeevent,asfollows:
componentWillMount=()=>{
this.subscription=NativeAppEventEmitter.addListener(
'backgroundProgress',
event=>this.setState({backgroundTaskStatus:event.status})
);
}
8. Whenthecomponentunmounts,weneedtoremovetheeventlistenerfrom
thepreviousstep,asfollows:
componentWillUnmount=()=>{
this.subscription.remove();
}
9. TheUIwillhavetwobuttonsthatwilleachneedamethodtocallwhen
pressed.TherunBackgroundTaskwillruntheloadInBackgroundmethodthatwe
definedandexportedfromthenativeiOSlayeron
theBackgroundTaskManagercustomnativemodule.TheincreaseCounterbuttonwill
simplyincreasethecounterpropertyonstateby1,servingtoshowhowthe
mainthreadisnotblocked,asfollows:
runBackgroundTask=()=>{
BackgroundTaskManager.loadInBackground();
}
increaseCounter=()=>{
this.setState({
counter:this.state.counter+1
});
}
10. TheUIoftheappwillconsistoftwobuttonstoshowthebuttons
andTextcomponentsfordisplayingthevaluessavedonstate.TheRun
TaskbuttonwillexecutetherunBackgroundTaskmethodtokickoffa
backgroundprocess,andthis.state.backgroundTaskStatuswillupdatetodisplay
anewstatusfortheprocess.Forthefivesecondsthatthebackground
processisrunning,pressingtheIncreaseCounterbuttonwillstillincrease
thecounterby1,demonstratingthatthebackgroundprocessisnon-
blocking,asshowninthefollowingsnippet:
render(){
return(
<Viewstyle={styles.container}>
<Button
containerStyle={styles.buttonContainer}
style={styles.buttonStyle}
onPress={this.runBackgroundTask}>
RunTask
</Button>
<Textstyle={styles.instructions}>
BackgroundTaskStatus:
</Text>
<Textstyle={styles.welcome}>
{this.state.backgroundTaskStatus}
</Text>
<Textstyle={styles.instructions}>
Pressing"IncreaseConter"buttonshowsthatthetaskis
notblockingthemainthread
</Text>
<Button
containerStyle={[
styles.buttonContainer,
styles.altButtonContainer
]}
style={styles.buttonStyle}
onPress={this.increaseCounter}
>
IncreaseCounter
</Button>
<Textstyle={styles.instructions}>
CurrentCount:
</Text>
<Textstyle={styles.welcome}>
{this.state.counter}
</Text>
</View>
);
}
11. Asafinalstep,let'slayoutandstyletheappwiththestylesblock,as
follows:
conststyles=StyleSheet.create({
container:{
flex:1,
justifyContent:'center',
alignItems:'center',
backgroundColor:'#F5FCFF',
},
welcome:{
fontSize:20,
textAlign:'center',
margin:10,
},
instructions:{
textAlign:'center',
color:'#333333',
marginBottom:5,
marginLeft:20,
marginRight:20
},
buttonContainer:{
width:150,
padding:10,
margin:5,
height:40,
overflow:'hidden',
borderRadius:4,
backgroundColor:'#FF5722'
},
altButtonContainer:{
backgroundColor:'#CDDC39',
marginTop:30
},
buttonStyle:{
fontSize:16,
color:'white'
}
});
Howitworks...
Inthisrecipe,wecreatedanativemodulesimilartothemodulecoveredin
theExposingcustomiOSmodulesrecipefromearlierinthischapter.Wedefined
thenativemoduletoperformarbitraryexecutioninthebackgroundoftheReact
Nativeapp.Inthisrecipethebackgroundprocessismadeupofthefollowing
threesteps:
1. Spawnanewthread.
2. Sleepforfivesecondsonthenewthread.
3. Afterthefivesecondsleep(simulatingtheendofarunningbackground
process),aneventisdispatchedfromtheiOSlayertotheReactNative
layer,lettingitknowthattheprocesshasbeencompleted.Thisis
accomplishedviatheOS'sGCDAPI.
ThepurposeoftheUIinthisappistoexhibitthatmultithreadinghasbeen
achieved.IfthebackgroundprocesswasexecutedintheReactNativelayer,due
toJavaScript'ssingle-threadednature,theappwouldhavelockedupforfive
secondswhilethatprocesswasrunning.Whenyoupressabutton,thebridgeis
invoked,whereuponmessagescanbepostedtothenativelayer.Ifthenative
threadiscurrentlybusysleeping,thenwecannotprocessthismessage.By
offloadingthatprocessingtoanewthread,bothcanbeexecutedatthesame
time.
BackgroundprocessingonAndroid
Inthisrecipewe'llbebuildingoutanAndroidequivalenttothepreviousrecipe.
ThisrecipewillalsousethenativeAndroidlayertocreateanewprocess,keep
thatprocessrunningbysleepingforfiveseconds,andallowuserinteractionvia
thebuttontoexhibitthattheapp'smainprocessingthreadisnotblocked.
Whiletheendresultwillbeverymuchthesame,spawninganewprocessinan
AndroidprojectishandledabitdifferentlyfromiOS.Thisrecipewillmakeuse
ofthenativeAsyncTaskfunction,specializedforhandlingshort-running
backgroundprocesses,toallowexecutionintheReactNativelayerwithout
blockingthemainthread.
Gettingready
Forthisrecipewe'llneedtocreateanewpureReactNativeapp.Let'sname
itMultiThreadingApp.
Wewillalsobeusingthereact-native-buttonlibrary.Installitwithnpm:
npminstallreact-native-button--save
Alternatively,wecanuseyarn:
npminstallreact-native-button--save
Howtodoit...
1. Let'sstartbyopeningtheAndroidprojectinAndroidStudio.InAndroid
Studio,selectOpenanexistingAndroidStudioprojectandopen
theandroiddirectoryofthenewproject.
2. We'llneedtwonewJava
classes:BackgroundTaskManagerandBackgroundTaskPackage.
3. Nowthatbothclasseshavebeencreated,let's
openBackgroundTaskManager.javaandbeginimplementingthenativemodule
thatwillwrapanAsyncTaskoperation,startingwithimportsanddefiningthe
class.Furthermore,likeanyothernativeAndroidmodule,we'llneedto
definethegetNamemethod,usedtoprovideReactNativewithanameforthe
module,asfollows:
packagecom.multithreadingapp;
importandroid.os.AsyncTask;
importcom.facebook.react.bridge.Arguments;
importcom.facebook.react.bridge.ReactApplicationContext;
importcom.facebook.react.bridge.ReactContextBaseJavaModule;
importcom.facebook.react.bridge.ReactMethod;
importcom.facebook.react.bridge.WritableMap;
importcom.facebook.react.modules.core.DeviceEventManagerModule;
publicclassBackgroundTaskManagerextendsReactContextBaseJavaModule{
publicBackgroundTaskManager(ReactApplicationContextreactApplicationContext){
super(reactApplicationContext);
}
@Override
publicStringgetName(){
return"BackgroundTaskManager";
}
//Definedinfollowingsteps
}
4. InordertoexecuteanAsyncTask,itneedstobesubclassedbyaprivateclass.
We'llneedtoaddanewprivateinnerBackgroundLoadTasksubclassforthis.
Beforewedefineit,let'sfirstaddaloadInBackgroundmethodthatwill
ultimatelybeexportedtotheReactNativelayer.Thismethodsimply
createsanewinstanceofBackgroundLoadTaskandcallsitsexecutemethod,as
follows:
publicclassBackgroundTaskManagerextendsReactContextBaseJavaModule{
//Definedinpreviousstep
@ReactMethod
publicvoidloadInBackground(){
BackgroundLoadTaskbackgroundLoadTask=newBackgroundLoadTask();
backgroundLoadTask.execute();
}
}
5. TheBackgroundLoadTasksubclasswillalsobeusingahelperfunctionfor
sendingeventsbackandforthacrosstheReactNativebridgeto
communicatethestatusofthebackgroundprocess.ThesendEventmethod
takesaneventNameandparamsasarguments,thenusesReact
Native'sRCTDeviceEventEmitterclasstoemittheevent,asfollows:
publicclassBackgroundTaskManagerextendsReactContextBaseJavaModule{
//Definedinstepsabove
privatevoidsendEvent(StringeventName,WritableMapparams){
getReactApplicationContext().getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit(eventName,params);
}
}
6. Nowlet'smoveontodefiningtheBackgroundLoadTasksubclass,which
extendsAsyncTask.Thesubclasswillbemadeupofthree
methods:doInBackgroundforspinningupanewthreadandsleepingitforfive
minutes,onProgressUpdateforsendinga"Loading"statustotheReactNative
layer,andonPostExecuteforsendinga"Done"statuswhenthebackgroundtask
hascompleted,asfollows:
publicclassBackgroundTaskManagerextendsReactContextBaseJavaModule{
//Definedinabovesteps
privateclassBackgroundLoadTaskextendsAsyncTask<String,String,String>{
@Override
protectedStringdoInBackground(String...params){
publishProgress("Loading");
try{
Thread.sleep(5000);
}catch(Exceptione){
e.printStackTrace();
}
return"Done";
}
@Override
protectedvoidonProgressUpdate(String...values){
WritableMapparams=Arguments.createMap();
params.putString("status","Loading");
sendEvent("backgroundProgress",params);
}
@Override
protectedvoidonPostExecute(Strings){
WritableMapparams=Arguments.createMap();
params.putString("status","Done");
sendEvent("backgroundProgress",params);
}
}
}
7. SincetheonlydifferencebetweentheiOSimplementationandtheAndroid
implementationlivesinthenativelayeroftherecipe,youcanfollowstep5
tostep11ofthepreviousrecipetoimplementtheJavaScriptportionofthe
app.
8. Thefinalappshouldbehaveandlook(asidefromdifferencesindevices)
thesameastheappinthepreviousrecipe.
Howitworks...
Inthisrecipe,wemimickedthefunctionalitywecreatedintheBackground
processingoniOSrecipeonAndroid.WecreatedanAndroidnativemodule
withamethodwhich,wheninvoked,performsarbitraryexecutioninthe
background(sleepforfiveseconds).Whentheprocessiscomplete,itemitsan
eventtotheReactNativelayer,whereuponweupdatetheappUItoreflectthe
statusofthebackgroundprocess.Androidhasmultipleoptionsforperforming
multithreadedoperationsnatively.Inthisrecipe,weusedAsyncTask,sinceitis
gearedtowardsshort-running(severalseconds)processes,itisrelativelysimple
toimplement,andtheoperatingsystemmanagesthreadcreationandresource
allocationforus.YoucanreadmoreaboutAsyncTaskintheofficialdocumentation
at:
https://developer.android.com/reference/android/os/AsyncTask
PlayingaudiofilesoniOS
InthechapterImplementingComplexUserInterfaces–PartIII,wecovered
buildingoutarelativelysophisticatedlittleaudioplayerintheCreatinganAudio
PlayerrecipeusingtheAudiocomponentprovidedbytheExpoSDK.Oneofthe
shortcomingofExpo'sAudiocomponent,however,isthatitcannotbeusedto
playaudiowhentheappisbackgrounded.Usingthenativelayeriscurrentlythe
onlywaytoachievethis.
Inthisrecipe,wewillcreateanativemoduletoshowtheiOSMediaPickerand
thenselectamusicfiletoplay.TheselectedfilewillplaythroughthenativeiOS
mediaplayer,whichallowsaudiotobeplayedwhentheappisbackgrounded,
andallowstheusertocontroltheaudioviathenativeiOScontrolcenter.
Gettingready
Forthisrecipe,we'llneedtocreateanewpureReactNativeapp.Let'scall
itAudioPlayerApp.
We'llalsobeusingthereact-native-buttonlibrary,whichcanbeinstalledwithnpm:
npminstallreact-native-button--save
Alternatively,wecanuseyarn:
yarnaddreact-native-button
Thisisarecipethatshouldonlybeexpectedtoworkonarealdevice.You'llalso
wanttomakesureyouhavemusicsyncedtotheiOSdeviceandavailableinthe
medialibrary.
Howtodoit...
1. Let'sstartbyopeningtheiOSProjectinXcodelocatedintheiosdirectory
ofthenewReactNativeapp.
2. Next,we'llcreateanewObjective-CCocoaclasscalledMediaManager.
3. IntheMediaManagerheader(.h)file,weneedto
importMPMediaPickerControllerandMPMusicPlayerController,alongwiththeReact
Nativebridge(RCTBridgeModule),asfollows:
#import<Foundation/Foundation.h>
#import<MediaPlayer/MediaPlayer.h>
#import<React/RCTBridgeModule.h>
#import<React/RCTEventDispatcher.h>
@interfaceMediaManager:NSObject<RCTBridgeModule,MPMediaPickerControllerDelegate>
@property(nonatomic,retain)MPMediaPickerController*mediaPicker;
@property(nonatomic,retain)MPMusicPlayerController*musicPlayer;
@end
4. First,wearegoingtoneedtoworkonaddingthenativeMediaPickerin
theMediaManagerimplementation(MediaManager.m).Thefirstmethodswillbefor
showingandhidingtheMediaPicker:showMediaPickerandhideMediaPicker,as
follows:
#import"MediaManager.h"
#import"AppDelegate.h"
@implementationMediaManager
RCT_EXPORT_MODULE();
@synthesizebridge=_bridge;
@synthesizemusicPlayer;
#pragmamarkprivate-methods
-(void)showMediaPicker{
if(self.mediaPicker==nil){
self.mediaPicker=[[MPMediaPickerControlleralloc]initWithMediaTypes:MPMediaTypeAnyAudio];

[self.mediaPickersetDelegate:self];
[self.mediaPickersetAllowsPickingMultipleItems:NO];
[self.mediaPickersetShowsCloudItems:NO];
self.mediaPicker.prompt=@"Selectsong";
}

AppDelegate*delegate=(AppDelegate*)[[UIApplicationsharedApplication]delegate];

[delegate.window.rootViewControllerpresentViewController:self.mediaPickeranimated:YEScompletion:nil];
}
voidhideMediaPicker(){
AppDelegate*delegate=(AppDelegate*)[[UIApplicationsharedApplication]delegate];
[delegate.window.rootViewControllerdismissViewControllerAnimated:YEScompletion:nil];
}
//Definedonfollowingsteps
@end
5. Next,we'llimplementthetwoactionsthat
themediaPickerneeds:didPickMediaItemsforpickingamediaitem,
andmediaPickerDidCancelforcancellingtheaction,asfollows:
-(void)mediaPicker:(MPMediaPickerController*)mediaPickerdidPickMediaItems:(MPMediaItemCollection*)mediaItemCollection{
MPMediaItem*mediaItem=mediaItemCollection.items[0];
NSURL*assetURL=[mediaItemvalueForProperty:MPMediaItemPropertyAssetURL];

[self.bridge.eventDispatchersendAppEventWithName:@"SongPlaying"
body:[mediaItemvalueForProperty:MPMediaItemPropertyTitle]];

if(musicPlayer==nil){
musicPlayer=[MPMusicPlayerControllersystemMusicPlayer];
}

[musicPlayersetQueueWithItemCollection:mediaItemCollection];
[musicPlayerplay];

hideMediaPicker();
}
-(void)mediaPickerDidCancel:(MPMediaPickerController*)mediaPicker{
hideMediaPicker();
}
6. Next,we'regoingtoneedtoexposeourMediaManagertotheReactNative
bridgeandcreateamethodthatwillbeinvokedtoshowtheMediaPicker,as
follows:
RCT_EXPORT_MODULE();
RCT_EXPORT_METHOD(showSongs){
[selfshowMediaPicker];
}
7. We'rereadytomoveontotheJavaScriptportion.Let'sstartbyadding
dependenciestoApp.js.WealsoneedtoimporttheMediaManagernative
modulewecreatedinstep3tostep6usingtheNativeModulescomponent,as
follows:
importReact,{Component}from'react';
import{
StyleSheet,
Text,
View,
NativeModules,
NativeAppEventEmitter
}from'react-native';
importButtonfrom'react-native-button';
constMediaManager=NativeModules.MediaManager;
8. Let'sdefinetheAppclassandtheinitialstate.ThecurrentSongpropertywill
holdthetrackinfoforthecurrentlyplayingsong,aspassedfromthenative
layer,asfollows:
exportdefaultclassAppextendsComponent{
state={
currentSong:null
}
//Definedonfollowingsteps
}
9. Whenthecomponentmounts,we'llsubscribetotheSongPlayingeventthat
willbeemittedfromthenativelayerwhenasongbeginsplaying.We'll
savetheeventlistenertoalocalsubscriptionclassvariablesothatwecan
cleanitupwiththeremovemethodwhenthecomponentunmounts,as
follows:
componentWillMount(){
this.subscription=NativeAppEventEmitter.addListener(
'SongPlaying',
this.updateCurrentlyPlaying
);
}
componentWillUnmount=()=>{
this.subscription.remove();
}
10. We'llalsoneedamethodforupdatingthecurrentSongvalueonstate,anda
methodforcallingtheshowSongsmethodonthenativeMediaManagermodulewe
definedinstep3tostep6,asfollows:
updateCurrentlyPlaying=(currentSong)=>{
this.setState({currentSong});
}
showSongs(){
MediaManager.showSongs();
}
11. TherendermethodwillbemadeupofaButtoncomponentforexecuting
theshowSongsmethodwhenpressed,andTextcomponentsfordisplayingthe
infoforthesongthat'scurrentlyplaying,asfollows:
render(){
return(
<Viewstyle={styles.container}>
<Button
containerStyle={styles.buttonContainer}
style={styles.buttonStyle}
onPress={this.showSongs}>
PickSong
</Button>
<Textstyle={styles.instructions}>SongPlaying:</Text>
<Textstyle={styles.welcome}>{this.state.currentSong}</Text>
</View>
);
}
12. Finally,we'lladdourstylesforlayingoutandstylingtheapp,asfollows:
conststyles=StyleSheet.create({
container:{
flex:1,
justifyContent:'center',
alignItems:'center',
backgroundColor:'#F5FCFF',
},
welcome:{
fontSize:20,
textAlign:'center',
margin:10,
},
instructions:{
textAlign:'center',
color:'#333333',
marginBottom:5,
},
buttonContainer:{
width:150,
padding:10,
margin:5,
height:40,
overflow:'hidden',
borderRadius:4,
backgroundColor:'#3B5998'
},
buttonStyle:{
fontSize:16,
color:'#fff'
}
});
Howitworks...
InthisrecipewecoveredhowtousetheMediaPlayeriniOSbywrappingits
functionalityinanativemodule.Themediaplayerframeworkallowsusto
accessthenativeiPodlibrary,andplayaudiofilesfromthelibraryonthedevice
usingthesamefunctionalityasthenativeiOSMusicapp.
PlayingaudiofilesonAndroid
AbenefitthatGooglelikestoclaimthatAndroidhasoveriOS,isflexibilityin
dealingwithfilestorage.AndroiddevicessupportexternalSDcardsthatcan
befilledwithmediafiles,anddonotneedaproprietarymethodofadding
multimediaasiOSdoes.
Inthisrecipe,wewilluseAndroid'snativeMediaPicker,whichisstartedfroman
intent.Wewillthenbeabletopickasongandhaveitplaythroughour
application.
Gettingready
Forthisrecipe,wecreatedaReactNativeapplicationtitledAudioPlayer.
Inthisrecipe,wewillusethereact-native-buttonlibrary.Toinstallit,runthe
followingcommandintheterminalfromyourprojectrootdirectory:
$npminstallreact-native-button--save
MakesureyouhavemusicfilesavailableinyourMusic/directoryonyour
Androiddeviceoremulator.
Howtodoit...
1. Let'sstartbyopeningtheAndroidprojectusingAndroidStudio.InAndroid
Studio,selectOpenanexistingAndroidStudioprojectandopen
theandroiddirectoryoftheproject.
2. We'llneedtwonewJavaclassesforthisrecipe:MediaManagerandMediaPackage.
3. OurMediaManagerwilluseintentstoshowthemediaPicker,MediaPlayertoplay
music,andMediaMetadataRetrievertoparsemetadatainformationfromthe
audiofiletosendbacktotheJavaScriptlayer.Let'sstartbyimportingallof
thedependencieswe'llneedintheMediaManager.javafile,asfollows:
importandroid.app.Activity;
importandroid.content.Intent;
importandroid.media.AudioManager;
importandroid.media.MediaMetadataRetriever;
importandroid.media.MediaPlayer;
importandroid.net.Uri;
importandroid.provider.MediaStore;
importcom.facebook.react.bridge.ActivityEventListener;
importcom.facebook.react.bridge.Arguments;
importcom.facebook.react.bridge.ReactApplicationContext;
importcom.facebook.react.bridge.ReactContextBaseJavaModule;
importcom.facebook.react.bridge.ReactMethod;
importcom.facebook.react.bridge.WritableMap;
importcom.facebook.react.modules.core.DeviceEventManagerModule;
4. Sincewe'recreatinganativemoduleandstartingintents,weneedtoadd
someboilerplatecodeaswellasimplementingourshowSongsmethod,as
follows:
publicclassMediaManagerextendsReactContextBaseJavaModuleimplementsActivityEventListener{
privateMediaPlayermediaPlayer=null;
privateMediaMetadataRetrievermediaMetadataRetriever=null;
publicMediaManager(ReactApplicationContextreactApplicationContext){
super(reactApplicationContext);
reactApplicationContext.addActivityEventListener(this);
}
@Override
publicStringgetName(){
return"MediaManager";
}
@Override
publicvoidonCatalystInstanceDestroy(){
super.onCatalystInstanceDestroy();
mediaPlayer.stop();
mediaPlayer.release();
mediaPlayer=null;
}
@ReactMethod
publicvoidshowSongs(){
Activityactivity=getCurrentActivity();
Intentintent=newIntent(Intent.ACTION_PICK,MediaStore.Audio.Media.EXTERNAL_CONTENT_URI);
activity.startActivityForResult(intent,10);
}
@Override
publicvoidonActivityResult(Activityactivity,intrequestCode,intresultCode,Intentdata){
if(data!=null){
playSong(data.getData());
}
}
@Override
publicvoidonNewIntent(Intentintent){
}
privatevoidplaySong(Uriuri){
try{
if(mediaPlayer!=null){
mediaPlayer.stop();
mediaPlayer.reset();
}else{
mediaMetadataRetriever=newMediaMetadataRetriever();
mediaPlayer=newMediaPlayer();
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
}
mediaPlayer.setDataSource(getReactApplicationContext(),uri);
mediaPlayer.prepare();
mediaPlayer.start();
mediaMetadataRetriever.setDataSource(getReactApplicationContext(),uri);
Stringartist=mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ARTIST);
StringsongTitle=mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_TITLE);
WritableMapparams=Arguments.createMap();
params.putString("songPlaying",artist+"-"+songTitle);
getReactApplicationContext()
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit("SongPlaying",params);
}catch(Exceptionex){
ex.printStackTrace();
}
}
}
5. ThecustommodulewillalsoneedtobeaddedtothegetPackagesarrayin
theMainApplication.javafile,asfollows:
protectedList<ReactPackage>getPackages(){
returnArrays.<ReactPackage>asList(
newMainReactPackage(),
newMediaPackage()
);
}
6. AscoveredintheExposingCustomAndroidModulesrecipeearlierinthis
chapter,wemustaddtherequisiteboilerplatetoMediaPackage.javafor
ourMediaManagercustommoduletobeexportedtotheReactNativelayer.
Refertothatrecipeforamorethoroughexplanation.Addtherequisite
boilerplateasfollows:
importcom.facebook.react.ReactPackage;
importcom.facebook.react.bridge.NativeModule;
importcom.facebook.react.bridge.ReactApplicationContext;
importcom.facebook.react.uimanager.ViewManager;
importjava.util.ArrayList;
importjava.util.Collections;
importjava.util.List;
publicclassMediaPackageimplementsReactPackage{
@Override
publicList<ViewManager>createViewManagers(ReactApplicationContextreactContext){
returnCollections.emptyList();
}
@Override
publicList<NativeModule>createNativeModules(ReactApplicationContextreactContext){
List<NativeModule>modules=newArrayList<>();
modules.add(newMediaManager(reactContext));
returnmodules;
}
}
7. TheJavaScriptlayerfortheAndroidappisidenticaltothatfoundinthe
previousiOSrecipe.Usestep7tostep10ofthisrecipetocompletethe
finalportionoftheapp.
Howitworks...
Thisrecipecoversplayingaudiofilesusingtheoperatingsystem'sbuilt-inaudio
support.IncontrasttoiOS,Androiddoesnothaveanoperatingsystem-levelUI
formediaplayback.Instead,theappmustdecidehowtheUIshouldbedesigned
andimplemented.
IntegrationwithNativeApplications
Inthischapter,wewillcoverthefollowingrecipes:
CombiningaReactNativeappandaNativeiOSapp
CommunicatingfromaniOSapptoReactNative
CommunicatingfromReactNativetoaniOSappcontainer
HandlingbeinginvokedbyanexternaliOSapp
EmbeddingaReactNativeappinsideaNativeAndroidapp
CommunicatingfromanAndroidapptoReactNative
CommunicatingfromReactNativetoanAndroidappcontainer
HandlingbeinginvokedbyanexternalAndroidapp
Introduction
ReactNativewasintroducedasasolutiontobuildnativeapplicationsusing
JavaScript,withthegoalofgrantingmoredeveloperstheabilitytobuildtruly
nativeapplicationsformultipleplatforms.AsaconsequenceofbuildingaReact
Nativeapplicationwithateam,itcanbecommonforJavaScriptdevelopersand
nativedeveloperstoworkcloselytogether.
OneoftheadvantagesofReactNative'sabilitytorendernativeUIviewsisthat
theycanbeeasilyembeddedinsideexistingnativeapps.Itisnotuncommonfor
companiestoalreadyhavesophisticatednativeappsthatarecriticaltotheirline
ofbusiness.Theremaybenoimmediateneedtorewritetheirentirecodebasein
ReactNativeiftheappisnotbroken.Insuchacase,ReactNativecanbe
leveragedbybothJavaScriptandnativedeveloperstowriteReactNativecode
thatcanbeintegratedintoanexistingapp.
ThischapterwillfocusexclusivelyonusingReactNativeinsideexistingnative
iOSandAndroidapplications.WewillcoverrenderingaReactNativeapp
withinanativeapp,howtocommunicatebetweentheReactNativeappandits
nativeparentapp,andhowourReactNativeappcanbeinvokedworkwithother
appsonauser'sdevice.
WhenworkingontheAndroidrecipes,itisrecommendedthatyouenabletheauto-import
settingsinAndroidStudiooruseAlt+Entertoperformaquickfixcodecompletionforthe
classimport.
CombiningaReactNativeappanda
NativeiOSapp
IntheeventthatyouworkforacompanyorhaveaclientthathasanactiveiOS
appoutintheworld,itmaynotbeadvantageoustorewriteitfromscratch,
especiallyifitiswell-built,usedfrequently,andpraisedbyitsusers.Ifyoujust
wanttobuildnewfunctionalityusingReactNative,theReactNativeappcanbe
embeddedandrenderedinsideanexistingnativeiOSapp.
ThisrecipewillwalkthroughcreatingablankiOSappandaddingittoaReact
Nativeappsothatthetwolayerscancommunicatewitheachother.Wewill
covertwowaysofrenderingtheReactNativeapp:embeddedinsidethe
applicationasanestedview,andanotherasafull-screenimplementation.The
stepsthatarediscussedinthisrecipeserveasabaselineforrenderingReact
Nativeapps,alongwithnativeiOSapps.
Gettingready
ThisrecipewillbereferencinganativeiOSapplicationnamedEmbeddedApp.We
willwalkthroughcreatingthesampleiOSapplicationinthissection.If
youalreadyhaveaniOSappyouintendonintegratingwithReactNative,you
canskipaheadtotherecipeinstructions.Youwill,however,needtobesurethat
youhavecocoapodsinstalled.ThislibraryisapackagemanagerforXcode
projects.ItcanbeinstalledviaHomebrewusingthefollowingcommand:
brewinstallcocoapods
Withcocoapodsinstalled,thenextstepiscreatinganewnativeiOSprojectin
Xcode.ThiscanbedonebyopeningXcodeandchoosingFile|New|Project.In
thewindowthatfollows,choosethedefaultSingleViewApplicationiOS
templatetogetstarted,andhitNext.
Intheoptionsscreenforthenewproject,besuretosettheProductNamefield
toEmbeddedApp:
Howtodoit...
1. We'llbeginbycreatinganewvanillaReactNativeappthatwillserveasthe
rootofourproject.Let'snamethenewprojectEmbedApp.Youcancreatethe
newReactNativeappwiththeCLIusingthefollowingcommand:
react-nativeinitEmbedApp
2. BycreatingthenewappwiththeCLI,theiosandandroidsubfolderswillbe
automaticallycreatedforus,holdingthenativecodeforeachplatform.
Let'smovethenativeappwecreatedintheGettingreadysectiontotheios
foldersothatitlivesat/EmbedApp/ios/EmbeddedApp.
3. Nowthatwehavethebasicstructureweneedfortheapp,we'llneedtoadd
aPodfile.Thisisafile,similartopackage.jsoninwebdevelopment,that
keepstrackofallofthecocoapoddependencies(calledpods)thatareused
inaproject.ThePodfileshouldalwaysliveintherootofthevanillaiOS
project,whichinourcaseis/EmbedApp/ios/EmbeddedApp.InaTerminal,cdinto
thisdirectoryandrunthepodinitcommand.ThisgeneratesabasePodfile
foryou.
4. Next,openthePodfileinyourfavoriteIDE.We'llbeaddingthepodsthat
areneededfortheapptothisfile.Thefollowingisthecontentsofthefinal
Podfile,withthenewlyaddedReactNativedependenciesinbold:
target'EmbeddedApp'do
#Uncommentthenextlineifyou'reusingSwiftorwouldliketousedynamicframeworks
#use_frameworks!
#PodsforEmbeddedApp
target'EmbeddedAppTests'do
inherit!:search_paths
#Podsfortesting
end
target'EmbeddedAppUITests'do
inherit!:search_paths
#Podsfortesting
end
#Podsthatwillbeusedintheapp
pod'React',:path=>'../../node_modules/react-native',:subspecs=>[
'Core',
'CxxBridge',#IncludethisforRN>=0.47
'DevSupport',#IncludethistoenableIn-AppDevmenuifRN>=0.43
'RCTText',
'RCTNetwork',
'RCTWebSocket',#Neededfordebugging
'RCTAnimation',#NeededforFlatListandanimationsrunningonnativeUIthread
#Addanyothersubspecsyouwanttouseinyourproject
]
#ExplicitlyincludeYogaifyouareusingRN>=0.42.0
pod'yoga',:path=>'../../node_modules/react-native/ReactCommon/yoga'
#Thirdpartydepspodspeclink
pod'DoubleConversion',:podspec=>'../../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec'
pod'glog',:podspec=>'../../node_modules/react-native/third-party-podspecs/glog.podspec'
pod'Folly',:podspec=>'../../node_modules/react-native/third-party-podspecs/Folly.podspec'
end
NoticehoweachofthepathslistedintheReactNativedependenciesthatwe'readdingpoint
tothe/node_modulesfolderoftheReactNativeproject.Ifyournativeproject(inour
case,EmbeddedApp)wasatadifferentlocation,thesereferencesto/node_moduleswouldhavetobe
updatedaccordingly.
5. WiththePodfileinplace,installingthepodsthemselvesisaseasyas
runningthepodinstallcommandfromtheTerminalinthesamedirectory
wecreatedthePodfile.
6. Next,let'sreturntotheReactNativeappattherootdirectoryofthe
project,/EmbedApp.We'llstartbyremovingthegeneratedcodeinindex.js,and
replacingitwithourownsimpleReactNativeapp.Thiswillbeavery
simpleappthatjustrendersthetextHelloinReactNativesothatitcanbe
distinguishedfromthenativelayerinlatersteps:
importReact,{Component}from'react';
import{
AppRegistry,
StyleSheet,
View,
Text
}from'react-native';
classEmbedAppextendsComponent{
render(){
return(
<Viewstyle={styles.container}>
<Text>HelloinReactNative</Text>
</View>
);
}
}
conststyles=StyleSheet.create({
container:{
flex:1,
justifyContent:'center',
alignItems:'center',
backgroundColor:'#F5FCFF',
}
});
AppRegistry.registerComponent('EmbedApp',()=>EmbedApp);
7. NowthatwehaveaReactNativeapp,wecanmovetothenativecode.
Whenweinitializedcocoapodsinstep3,italsogeneratedanew.xcworkspace
file.BesuretoclosetheEmbeddedAppprojectinXcode,thenre-openitin
XcodeusingtheEmbeddedApp.xcworkspacefile.
8. InXcode,let'sopenMain.storyboard:
9. Inthestoryboard,we'llneedtoaddtwobuttons:onelabeledOpenReact
NativeAppandonelabeledOpenReactNativeApp(Embedded).We'll
alsoneedanewcontainerviewbelowthetwobuttons.Theresulting
storyboardshouldlooksomethinglikethis:
10. Next,we'llneedanewanewCocoaTouchClass.Thiscanbecreatedfrom
themenusbychoosingFile|New|File.We'llnamethe
classEmbeddedViewControllerandassignitasubclassofUIViewController:
11. Let'sreturntoMain.storyboard.Inthenewscenethat'screatedbyaddingthe
classinthepreviousstep(secondViewControllerScene),selecttheView
Controllerchild.MakesurethattheIdentityinspectorisopenintheright-
handpanel:
WiththeViewControllerselected,changetheClassvaluetoournewly
createdclass,EmbeddedViewController:
12. Next,inthetopViewControllerScene,selecttheEmbedsegueobject:
13. Withthesegueselected,selecttheAttributesinspectorfromtheright-hand
panel,andupdatetheIdentifierfieldtotheembedvalue.Wewillusethis
identifiertoembedtheReactNativelayerwithinthenativeapp:
14. We'rereadytobuildouttheViewControllerimplementation.Open
theViewController.mfile.We'llstartwiththeimports:
#import"ViewController.h"
#import"EmbeddedViewController.h"
#import<React/RCTRootView.h>
15. Justbeneaththeimports,wecanaddaninterfacedefinitiontopointtothe
EmbeddedViewControllerwecreatedinstep10:
@interfaceViewController(){
EmbeddedViewController*embeddedViewController;
}
@end
16. Followingisthe@interface,we'lladdthemethodsweneedtothe
@implementation.Thefirstmethod,openRNAppButtonPressed,willbewiredtothe
firstbuttonwecreatedinthestoryboard,labeledOpenReactNativeApp.
Likewise,theopenRNAppEmbeddedButtonPressedmethodwillbewiredtothe
secondbutton,OpenReactNativeApp(Embedded).
You'lllikelynoticethatthemethodsarealmostidentical,withthesecond
methodreferencingembeddedViewController,thesameEmbeddedViewControllerclass
wecreatedinstep10([embeddedViewControllersetView:rootView];).Both
methodsdefinejsCodeLocationwiththevalue
ofhttp://localhost:8081/index.bundle?platform=ios,whichistheURLthatthe
ReactNativeappwillbeservedfrom.Also,takenotethatthemoduleName
propertyinbothmethodsissettoEmbedApp,whichisthenamethattheReact
Nativeappisexportedas,whichwedefinedinstep6:
@implementationViewController
-(void)viewDidLoad{
[superviewDidLoad];
//Doanyadditionalsetupafterloadingtheview,typicallyfromanib.
}
-(void)didReceiveMemoryWarning{
[superdidReceiveMemoryWarning];
//Disposeofanyresourcesthatcanberecreated.
}
-(IBAction)openRNAppButtonPressed:(id)sender{
NSURL*jsCodeLocation=[NSURL
URLWithString:@"http://localhost:8081/index.bundle?platform=ios"];
RCTRootView*rootView=
[[RCTRootViewalloc]initWithBundleURL:jsCodeLocation
moduleName:@"EmbedApp"
initialProperties:nil
launchOptions:nil];

UIViewController*vc=[[UIViewControlleralloc]init];
vc.view=rootView;
[selfpresentViewController:vcanimated:YEScompletion:nil];
}
-(IBAction)openRNAppEmbeddedButtonPressed:(id)sender{
NSURL*jsCodeLocation=[NSURL
URLWithString:@"http://localhost:8081/index.bundle?platform=ios"];
RCTRootView*rootView=
[[RCTRootViewalloc]initWithBundleURL:jsCodeLocation
moduleName:@"EmbedApp"
initialProperties:nil
launchOptions:nil];

[embeddedViewControllersetView:rootView];
}
//Definedinnextstep
@end
17. We'llalsoneedtodefinetheprepareForSeguemethod.Here,youcan
seesegue.identifierisEqualToString:@"embed",whichreferstotheembed
identifierwegavethesegueinstep13:
//Definedinprevioussteps
-(void)prepareForSegue:(UIStoryboardSegue*)seguesender:(id)sender{
if([segue.identifierisEqualToString:@"embed"]){
embeddedViewController=segue.destinationViewController;
}
}
@end
18. WithourimplementationofViewControllerinplace,wenowweneedtowire
upourbuttonactionstothebuttonsthemselves.Let'sreturn
toMain.storyboard.Ctrl+clickonthefirstbuttontogetamenuofactions
thatareassignabletothebutton,selecttheTouchUpInsideactionby
clickinganddraggingfromTouchUpInsidebacktothestoryboard,and
mapthebuttontotheopenRNAppButtonPressedmethodwedefinedinstep15.
Repeatthesestepsforthesecondbutton,linkingitinsteadtothe
openRNAppEmbeddedButtonPressedmethod:
19. FortheReactNativelayertobeabletocommunicatewiththenativelayer,
wealsoneedtoaddasecurityexception,whichwillallowourcodeto
communicatewithlocalhost.Right-clickontheInfo.plistfileandselect
OpenAs|SourceCode.Withinthebase<dict>tag,addthefollowingentry:
<key>NSAppTransportSecurity</key>
<dict>
<key>NSExceptionDomains</key>
<dict>
<key>localhost</key>
<dict>
<key>NSTemporaryExceptionAllowsInsecureHTTPLoads</key>
<true/>
</dict>
</dict>
</dict>
20. Ourappiscomplete!Fromthe/EmbedApprootdirectory,startuptheReact
NativeappusingtheCLIwiththefollowingcommand:
react-nativestart
21. WiththeReactNativeapprunning,let'salsoruntheNativeappfrom
Xcode.Now,pressingtheOpenReactNativeAppbuttonshouldopenthe
ReactNativeappwecreatedinstep6infullscreen,andthesameReact
Nativeappshouldopenwithinthecontainerviewwecreatedinstep9
whenpressingtheOpenReactNativeApp(Embedded)button.
Howitworks...
Inthisrecipe,wecoveredrenderingaReactNativeappwithinanativeiOSapp
viatwodifferentmethods.Thefirstmethodreplacestheapplication'smain
UIViewControllerinstancewiththeReactNativeapp,referredtointhenativecode
asRCTRootView.ThiswasaccomplishedintheopenRNAppButtonPressedmethod.The
secondandslightlymoreinvolvedmethodinvolvesrenderingtheReactNative
appinlinewiththenativeapp.Thiswasaccomplishbycreatingacontainerview
thatlinkstoadifferentUIViewControllerinstance.Inthiscase,wereplaced
thecontentsofembedViewControllerwithourRCTRootViewinstance.Thisiswhat
happenswhentheopenRNAppEmbeddedButtonPressedmethodisfired.
Seealso
ForabetterunderstandingoftherolecocoapodsplaysinXcode/ReactNative
development,IrecommendGoogle'sRoute85Showepisodecoveringthe
subjectonYouTube.Thevideocanbefoundathttps://www.youtube.com/watch?v=iEAjv
NRdZa0.
CommunicatingfromaniOSappto
ReactNative
Inthepreviousrecipe,welearnedhowtorenderaReactNativeappaspartofa
largernativeiOSapp.Unlessyou'rebuildingaglorifiedappcontainerorportal,
you'lllikelyneedtocommunicatebetweenthenativelayerandtheReactNative
layer.Thiswillbethesubjectmatterofthenexttworecipes,onerecipeforeach
directionofcommunication.
Inthisrecipe,wewillcovercommunicatingfromthenativelayertotheReact
Nativelayer,sendingdatafromtheparentiOSapptoourembeddedReact
Nativeapp,byusingaUITextFieldintheiOSappthatsendsitsdatatotheReact
Nativeapp.
Gettingready
SincethisreciperequiresanativeappwithanestedReactNativeappwithinit,
we'llbebeginningattheendofthepreviousrecipe,effectivelypickingupwhere
weleftoff.Thiswillhelpyouunderstandhowbasiccross-layercommunication
workssothatyoucanusethesameprinciplesinyourownnativeapp,which
mayalreadyexistandhavecomplexfeatures.Therefore,theeasiestwayto
followalongwiththisrecipeistousetheendpointofthepreviousrecipeasa
startingplace.
Howtodoit...
1. Let'sstartbyupdatingtheViewController.mimplementationfileinthenative
layer.BesuretoopentheprojectinXcodeviathe.xcworkspacefileinthe
EmbeddedApp,whichweplacedinthe/ios/EmbeddAppdirectoryoftheprojectin
thepreviousrecipe.We'llstartwiththeimports:
#import"ViewController.h"
#import"EmbeddedViewController.h"
#import<React/RCTRootView.h>
#import<React/RCTBridge.h>
#import<React/RCTEventDispatcher.h>
2. ThenextstepistoaddareferencetotheReactNativebridgevia
theViewControllerinterface,effectivelylinkingthenativecontrollerwiththe
ReactNativecode:
@interfaceViewController()<RCTBridgeDelegate>{
EmbeddedViewController*embeddedViewController;
RCTBridge*_bridge;
BOOLisRNRunning;
}
3. Wewillalsoneedan@propertyreferenceofuserNameFieldthatwewilluseina
latersteptowiretotheUITextField:
@property(weak,nonatomic)IBOutletUITextField*userNameField;
@end
4. Directlybelowthisreference,we'llbegindefiningtheclassmethods.We'll
beginwiththesourceURLForBridgemethod,whichdefineswheretheReact
Nativeappwillbeservedfrom.Inourcase,theappURLshould
behttp://localhost:8081/index.bundle?platform=ios,whichpointsattheindex.js
fileoftheReactNativeapponceitisrunwiththereact-nativestart
command:
-(NSURL*)sourceURLForBridge:(RCTBridge*)bridge{
NSURL*jsCodeLocation=[NSURL
URLWithString:@"http://localhost:8081/index.bundle?platform=ios"];
returnjsCodeLocation;
}
5. We'llleavetheviewDidLoadanddidReveiveMemoryWarningmethodsasis:
-(void)viewDidLoad{
[superviewDidLoad];
}
-(void)didReceiveMemoryWarning{
[superdidReceiveMemoryWarning];
//Disposeofanyresourcesthatcanberecreated.
}
6. Next,we'llneedtoupdatetheopenRNAppEmbeddedButtonPressedmethod.Notice
howthemoduleNamepropertyissettoFromNativeToRN.Thisisareferencetothe
namethatwegivetheReactNativeappwhenitisexported,whichwe'll
defineinalaterstep.Thistime,wearealsodefiningapropertyofuserName
forpassingdatatotheReactNativelayer:
-(IBAction)openRNAppEmbeddedButtonPressed:(id)sender{
NSString*userName=_userNameField.text;
NSDictionary*props=@{@"userName":userName};

if(_bridge==nil){
_bridge=[[RCTBridgealloc]initWithDelegate:self
launchOptions:nil];
}

RCTRootView*rootView=
[[RCTRootViewalloc]initWithBridge:_bridge
moduleName:@"FromNativeToRN"
initialProperties:props];

isRNRunning=true;
[embeddedViewControllersetView:rootView];
}
7. We'llalsoneedanonUserNameChangedmethod.Thisisthemethodthatwilldo
theactualsendingofdataacrossthebridgetotheReactNativelayer.The
eventnamewe'redefininghereisUserNameChanged,whichwe'llreferencein
theReactNativelayerinalaterstep.Thiswillalsopassalongthetextthat's
currentlyinthetextinput,whichwillbenameduserNameField:
-(IBAction)onUserNameChanged:(id)sender{
if(isRNRunning==YES&&_userNameField.text.length>3){
[_bridge.eventDispatchersendAppEventWithName:@"UserNameChanged"body:@{@"userName":_userNameField.text}];
}
}
8. We'llalsoneedprepareForSegueforconfiguringembeddedViewControllerjust
beforeitisdisplayed:
-(void)prepareForSegue:(UIStoryboardSegue*)seguesender:(id)sender{
if([segue.identifierisEqualToString:@"embed"]){
embeddedViewController=segue.destinationViewController;
}
}
@end
9. BackintheMain.storyboard,let'saddthatTextField,alongwithaLabelthat
defineswhattheinputisfor.YoucanalsonametheinputUserName
FieldsothateverythingiseasiertorecognizeintheViewControllerScene:
10. Next,we'llneedtowireaneventforwhenthetextchangesintheUser
NameFieldtextinput,andareferencingoutletsothattheViewController
knowshowtoreferenceit.ThesecanbothbedoneviatheConnections
Inspector,whichisaccessibleviathelastbuttonalongthetopoftheright-
handsidepanel(theiconisarightpointingarrowinacircle).Withthetext
inputselected,clickanddragfromEditingChangedtotheViewController
(representedviathemainstoryboard),andchoosetheonUserNameChange
methodwedefinedinstep7.Then,createthefollowingwiringsby
draggingtheitemtotheViewController.Similarly,addanewReferencing
OutletbyclickinganddraggingfromtheNewReferencingOutletbackto
theViewController,thistimechoosingtheuserNameFieldvaluewe
targetedinstep7.YourConnectionsInspectorsettingsshouldnowlooklike
this:
11. We'venowcompletedthestepsneededinthenativeapp.Let'smoveonto
theReactNativelayer.Backintheindex.jsfile,we'llstartwithimports.
Noticehowwe'renowincludingtheNativeAppEventEmitter.
12. Putthefollowingfunctionsinsidetheclassdefinition:
importReact,{Component}from'react';
import{
AppRegistry,
StyleSheet,
View,
Text,
NativeAppEventEmitter
}from'react-native';
13. We'llnametheappFromNativeToRNtomatchthemodulenamewedefinedin
thenativelayerinstep6,usingAppRegistry.registerComponenttoregisterthe
appwiththesamename.We'llalsoleavethebasicstylesinplace:
classFromNativeToRNextendsComponent{
//Definedinfollowingsteps
}
conststyles=StyleSheet.create({
container:{
flex:1,
justifyContent:'center',
alignItems:'center',
backgroundColor:'#F5FCFF',
}
});
AppRegistry.registerComponent('FromNativeToRN',()=>FromNativeToRN);
14. We'llsetaninitialstateobjectwithauserNamestringpropertytostoringand
displayingthetextthat'sreceivedfromthenativelayer:
classFromNativeToRNextendsComponent{
state={
userName:''
}
//Definedinfollowingsteps
}
15. TheuserNamevaluepassedintotheReactNativelayerwillbereceivedasa
property.Whenthecomponentmounts,wewanttodotwothings:set
theuserNamestatepropertyifit'salreadydefinedbythenativelayer,andwire
aneventlistenertoupdateuserNamewhenthetextfieldinthenativelayeris
updated.Recallinstep7thatwedefinedtheevent'snameto
beUserNameChanged,sothat'stheeventwe'lllistenfor.Whentheeventis
received,weupdatethestate.userNametothetextthat'spassedalongwiththe
event:
componentWillMount(){
this.setState({
userName:this.props.userName
});
NativeAppEventEmitter.addListener('UserNameChanged',(body)=>{
this.setState({userName:body.userName});
});
}
16. Finally,wecanaddtherenderfunction,whichsimplyrendersthevalue
storedinstate.userName:
render(){
return(
<Viewstyle={styles.container}>
<Text>Hello{this.state.userName}</Text>
</View>
);
}
17. It'stimetorunourapp!First,intherootoftheproject,wecanstartupthe
ReactNativeappwiththeReactNativeCLIwiththefollowingcommand:
react-nativestart
WefollowthisbyrunningthenativeappinthesimulatorviaXcode:
CommunicatingfromReactNativeto
aniOSappcontainer
Thelastrecipecoveredcommunicationbetweenlayersinthedirectionofnative
toReactnative.Inthisrecipe,wewillcovercommunicatingintheopposite
direction:fromReactNativetonative.Thistime,wewillrenderauserinput
elementinsideourReactNativeappandsetupaone-waybindingfromReact
NativetoaUIcomponentrenderedinthenativeapp.
Gettingready
Justlikethelastrecipe,thisrecipedependsonthefinalproductofthefirstapp
inthischapter,intheCombiningaReactNativeappandaNativeiOS
apprecipe.Tofollowalong,besureyou'vefinishedthatrecipe.
Howtodoit...
1. Let'sbegininthenativelayer.OpentheEmbeddedAppnativeappinXcodevia
the.xcworkspacefile.We'llfirstaddimportstoViewController.m:
#import"ViewController.h"
#import"EmbeddedViewController.h"
#import<React/RCTRootView.h>
#import<React/RCTBridge.h>
#import<React/RCTEventDispatcher.h>
2. Aswedidinthelastrecipe,weneedtoaddareferencetotheReactNative
bridgeviatheViewControllerinterface,providingabridgebetweenthenative
controllerandtheReactNativecode:
@interfaceViewController()<RCTBridgeDelegate>{
EmbeddedViewController*embeddedViewController;
RCTBridge*_bridge;
BOOLisRNRunning;
}
3. Wewillalsoneeda@propertyreferenceofuserNameFieldthatwewilluseina
latersteptowiretotheUITextField:
@property(weak,nonatomic)IBOutletUITextField*userNameField;
@end
4. Let'smoveontodefiningthe@implementation.Again,wemustprovidethe
sourceoftheReactNativeapp,whichwillbeservedfromlocalhost:
@implementationViewController
-(NSURL*)sourceURLForBridge:(RCTBridge*)bridge{
NSURL*jsCodeLocation=[NSURL
URLWithString:@"http://localhost:8081/index.bundle?platform=ios"];
returnjsCodeLocation;
}
5. UsingtheviewDidLoadmethod,wecanalsoconnectthecontrollertothe
methodthatopenstheReactNativeappinourcontainerview
(openRNAppEmbeddedButtonPressed).We'llleavethedidReveiveMemoryWarningmethod
asis:
-(void)viewDidLoad{
[superviewDidLoad];
[selfopenRNAppEmbeddedButtonPressed:nil];
}
-(void)didReceiveMemoryWarning{
[superdidReceiveMemoryWarning];
//Disposeofanyresourcesthatcanberecreated.
}
6. Likethelastrecipe,we'llneedtoupdatetheopenRNAppEmbeddedButtonPressed
method.Thistime,themoduleNamepropertyissettoFromRNToNativetoreflectthe
namethatwewillgivetheReactNativeappwhenitisexported,asdefined
inalaterstep.WealsodefineapropertyofuserNameforpassingdatatothe
ReactNativelayer:
-(IBAction)openRNAppEmbeddedButtonPressed:(id)sender{
if(_bridge==nil){
_bridge=[[RCTBridgealloc]initWithDelegate:selflaunchOptions:nil];
}

RCTRootView*rootView=
[[RCTRootViewalloc]initWithBridge:_bridge
moduleName:@"FromRNToNative"
initialProperties:nil];

isRNRunning=true;
[embeddedViewControllersetView:rootView];
}
7. Thelasttwomethodswe'llneedinthisfileareprepareForSeguefor
configuringtheembeddedViewControllerjustbeforeitisdisplayed,and
anupdateUserNameFieldmethodthatwillbefiredwhenourtextinputinthe
nativelayerisupdatedwithnewtextfromtheuser:
-(void)prepareForSegue:(UIStoryboardSegue*)seguesender:(id)sender{
if([segue.identifierisEqualToString:@"embed"]){
embeddedViewController=segue.destinationViewController;
}
}
-(void)updateUserNameField:(NSString*)userName{
[_userNameFieldsetText:userName];
}
@end
8. Unlikethepreviousrecipe,we'llneedtoalsoupdate
theViewControllerheaderfile(ViewController.h).Themethodreferenced
here,updateUserNameField,willbeusedwhenwedefinetheViewController
implementation:
#import<UIKit/UIKit.h>
@interfaceViewController:UIViewController
-(void)updateUserNameField:(NSString*)userName;
@end
9. Next,we'regoingtoneedtocreateanewUserNameManagernativemodule.
First,createaCocoaTouchclassnamedUserNameManager.Oncecreated,let's
opentheimplementationfile(UserNameManger.m)andaddourimports:
#import"UserNameManager.h"
#import"AppDelegate.h"
#import"ViewController.h"
#import<React/RCTBridgeModule.h>
Foramorein-depthlookatcreatingnativemodules,refertotheExposingCustom
iOSModulesrecipeinChapter11,AddingNativeFunctionality.
10. Then,we'lldefinetheclassimplementation.Themaintakeawayhereis
thesetUserNamemethod,whichisthemethodthatwe'reexportingfromthe
nativelayerforuseintheReactNativeapp.We'llusethismethodinthe
ReactNativeapptoupdatethevalueinthenativeTextField.However,
sinceweareupdatinganativeUIcomponent,theoperationmustbe
performedonthemainthread.ThisisthepurposeofthemethodQueue
function,whichinstructsthemoduletoexecuteonthemainthread:
@implementationUserNameManager
RCT_EXPORT_MODULE();
-(dispatch_queue_t)methodQueue
{
returndispatch_get_main_queue();
}
RCT_EXPORT_METHOD(setUserName:(NSString*)userName){
AppDelegate*delegate=(AppDelegate*)[[UIApplicationsharedApplication]delegate];
ViewController*controller=(ViewController*)delegate.window.rootViewController;

[controllerupdateUserNameField:userName];
}
@end
11. We'llalsoneedtoupdatetheUserNameMangager.hheaderfiletousetheReact
Nativebridgemodule:
#import<Foundation/Foundation.h>
#import<React/RCTBridgeModule.h>
@interfaceUserNameManager:NSObject<RCTBridgeModule>
@end
12. Likethelastrecipe,we'llneedtoaddaTextFieldandLabelfortheUser
Nameinput:
13. We'llalsoneedtoaddaReferencingOutletfromtheTextFieldwecreated
inthelastsettoouruserNameFieldproperty:
IfyouneedmoreinformationonhowtocreateaReferencingOutlet,viewstep10ofthe
previousrecipe.
14. We'refinishedwiththenativeportionofthisproject,solet'sturntoour
ReactNativecode.Let'sopentheindex.jsfileattherootoftheproject.
We'llstartwithourimports:
importReact,{Component}from'react';
import{
AppRegistry,
StyleSheet,
View,
Text,
TextInput,
NativeModules
}from'react-native';
15. Let'sdefinetheappwiththenameFromRNToNativetolineupwiththemoduleName
wedeclaredinthenativecodeinstep6,andregisterthecomponentwith
thesamename.ThestateobjectonlyneedsauserNamestringpropertyfor
holdthevaluethat'ssavedtotheTextInputcomponent,whichwe'lladdinthe
component'srenderfunction:
classFromRNToNativeextendsComponent{
state={
userName:''
}
//Definedonnextstep
}
AppRegistry.registerComponent('FromRNToNative',()=>FromRNToNative);
16. Theapp'srenderfunctionusesaTextInputcomponenttotakeinputfromthe
user,whichitwillthensendtothenativeappviatheReactNativebridge.It
doesthisbycallingtheonUserNameChangemethodwhenthevalueofthe
TextInputchanges:
render(){
return(
<Viewstyle={styles.container}>
<Text>EnterUserName</Text>
<TextInput
style={styles.userNameField}
onChangeText={this.onUserNameChange}
value={this.state.userName}
/>
</View>
);
}
17. ThelastthingweneedtodoisdefinetheonUserNameChangemethodthat'sused
bytheonChangeTextpropertyoftheTextInputcomponentwedefinedinthe
previousstep.Thismethodupdatesstate.userNametothevalueinthetext
input,andalsosendsthevaluealongtothenativecodebyusingthe
NativeModulescomponentinReactNative.NativeModuleshastheUserNameManager
classwedefinedasaCocoaTouchclassinthenativelayerinstep9.We
callthesetUserNamemethodthatwedefinedontheclassinstep10topassthe
valuealongtothenativelayer,whereitwillbedisplayedintheText
Fieldwecreatedinstep12:
onUserNameChange=(userName)=>{
this.setState({userName});
NativeModules.UserNameManager.setUserName(userName);
}
18. Theappisdone!ReturntotherootoftheprojecttostartuptheReact
Nativeappwiththefollowingcommand:
react-nativestart
Then,withtheReactNativeappstarted,runthenativeEmbeddedAppproject
fromXcode.Now,theinputintheReactNativeappshouldcommunicate
itsvaluetotheinputintheparentnativeapp:
Howitworks...
TocommunicatefromourReactNativeapptotheparentnativeapp,wecreated
anativemodulenamedUserNameManagerwithasetUserNamemethod,whichwe
exportedfromthenativelayer,andusedintheReactNativeapp,inits
onUserNameChangemethod.Thisistherecommendedwayofcommunicatingfrom
ReactNativetonative.
Handlingbeinginvokedbyan
externaliOSapp
Itisalsoacommonbehaviorfornativeappstocommunicatebetweenone
anothervialinking,andareusuallypromptedtotheuserwiththephraseOpen
in...,alongwiththenameofanappthatcanbetterhandleanaction.Thisisdone
byusingaprotocolthatisspecifictoyourapp.Justlikeanywebsitelinkhasa
protocolofeitherhttp://orhttps://,wecanalsocreateacustomprotocolthat
willallowanyotherapptoopenandsenddatatoourapp.
Inthisrecipe,wewillbecreatingacustomprotocolcalledinvoked://.Byusing
theinvoked://protocol,anyotherappcanuseittorunourappandpassdatatoit.
Gettingready
Forthisrecipe,we'llbestartingfromanewvanillaReactNativeapp.Let'sname
itInvokeFromNative.
Howtodoit...
1. Let'sstartbyopeningthenativelayerofthenewprojectinXcode.Thefirst
thingweneedtodoisadjusttheproject'sBuildSettings.Thiscanbedone
byselectingtherootprojectintheleftpanel,thenchoosingtheBuild
Settingstabalongthetopofthemiddlepanel:
2. We'llneedtoaddanewentrytotheHeaderSearchPathsfield:
FortheprojecttoknowthelocationoftheReactNativeJavaScript,it
needsthe$(SRCROOT)/../node_modules/react-native/Librariesvalue.Let'saddit
asarecursiveentry:
3. Wealsoneedtoregisterourcustomprotocol,whichwillbeusedbyother
apps.OpentheInfo.plistfileassourcecode(right-clickthenOpenAs
|SourceCode).Let'saddanentrytothefilethatwillregisterour
applicationundertheinvoked://protocol:
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLSchemes</key>
<array>
<string>invoked</string>
</array>
</dict>
</array>
4. Next,weneedtoaddtheRCTLinkingManagertotheAppDelegateimplementation,
whichlivesinAppDelegate.m,andwireittoourapp:
#import"AppDelegate.h"
#import<React/RCTBundleURLProvider.h>
#import<React/RCTRootView.h>
#import<React/RCTLinkingManager.h>
@implementationAppDelegate
//TherestoftheAppDelegateimplementation
-(BOOL)application:(UIApplication*)application
openURL:(NSURL*)url
options:(NSDictionary<UIApplicationOpenURLOptionsKey,id>*)options
{
return[RCTLinkingManagerapplication:applicationopenURL:urloptions:options];
}
@end
5. Now,let'smoveontotheReactNativelayer.Insideindex.js,we'lladdour
imports,whichincludestheLinkingcomponent:
importReact,{Component}from'react';
import{
AppRegistry,
StyleSheet,
Text,
View,
Linking
}from'react-native';
6. Next,we'llcreatetheclassdefinitionandregisterthecomponent
asInvokeFromNative.We'llalsodefineaninitialstateobjectwithastatusstring
propertysettothevalue'AppRunning':
classInvokeFromNativeextendsComponent{
state={
status:'AppRunning'
}
//Definedonfollowingsteps
}
AppRegistry.registerComponent('InvokeFromNative',()=>InvokeFromNative);
7. Now,we'llusethemountandunmountlifecyclehookstoadd/removethe
eventlistenerfortheinvoked://protocol.Whentheeventisheard,the
onAppInvokedmethod,whichisdefinedinthenextstep,willbefired:
componentWillMount(){
Linking.addEventListener('url',this.onAppInvoked);
}
componentWillUnmount(){
Linking.removeEventListener('url',this.onAppInvoked);
}
8. TheonAppInvokedfunctionsimplytakestheeventfromtheeventlistenerand
updatesstate.statustoreflectthatinvocationhashappened,displayingthe
protocolviaevent.url:
onAppInvoked=(event)=>{
this.setState({
status:`AppInvokedby${event.url}`
});
}
9. Therendermethod'sonlyrealpurposeinthisrecipeistorenderthestatus
propertyonstate:
render(){
return(
<Viewstyle={styles.container}>
<Textstyle={styles.instructions}>
AppStatus:
</Text>
<Textstyle={styles.welcome}>
{this.state.status}
</Text>
</View>
);
}
10. We'llalsoaddafewbasicstylestocenterandsizethetext:
conststyles=StyleSheet.create({
container:{
flex:1,
justifyContent:'center',
alignItems:'center',
backgroundColor:'#F5FCFF',
},
welcome:{
fontSize:20,
textAlign:'center',
margin:10,
},
instructions:{
textAlign:'center',
color:'#333333',
marginBottom:5,
},
});
11. Ourappisfinished.Onceyou'vestartedrunningtheapp,youshouldsee
somethinglikethis:
12. Withtheapprunning,wecansimulatetheactionofanotherappopening
ourReactNativeappusingtheinvoked://protocol.Thiscanbedonewith
thefollowingTerminalcommand:
xcrunsimctlopenurlbootedinvoked://
Onceinvoked,theappshouldupdatetoreflecttheinvocation:
Howitworks...
Inthisrecipe,wecoveredhowtoregisteracustomprotocol(orURLschema)
forallowingourapptobeinvokedbyotherapps.Theaimofthisrecipewasto
keepourexampleassimpleaspossible,sowedidnotbuildoutthehandling
datawepassedtoanappviathelinkingmechanism.However,itisentirely
possibletodosoiftheneedsofyourapprequireit.Foradeeperdiveonthe
Linkingcomponent,checkouttheofficialdocumentsathttps://facebook.github.io/re
act-native/docs/linking.
EmbeddingaReactNativeappinside
aNativeAndroidapp
SincetheAndroidplatformstillholdsthemajoritystakeinthesmartphone
marketspace,it'slikelythatyou'llwanttobuildtheappforbothAndroidaswell
asiOS.AlargeadvantageofReactNativedevelopmentismakingthisprocess
easier.ButwhathappenswhenyouwanttowriteanewfeatureusingReact
NativeforaworkingAndroidappthat'salreadybeenpublished?
Fortunately,ReactNativemakesthispossibleaswell.
ThisrecipewillcovertheprocessofembeddingaReactNativeappinsidean
existingAndroidappbydisplayingtheReactNativeappinsideacontainerview.
Thestepshereareusedasabaselinefortherecipesthatfollow,whichinvolve
communicationwithaReactNativeapp.
Gettingready
Inthissection,wewillcreateasampleAndroidapplicationusingAndroid
StudiocalledEmbedApp.IfyouhaveabaseAndroidapplicationyouwouldliketo
workwith,youcanskipthesestepsandproceedtotheactualimplementation:
1. OpenAndroidStudioandcreateanewproject(File|NewProject)
2. SettheapplicationnametoEmbeddedAppandfilloutyourcompanydomain.
PressNext
3. LeaveEmptyActivityselectedasthedefaultandpressNext
4. LeavetheActivitypropertiesastheyarebydefaultandpressFinish
Howtodoit...
1. Atthispoint,ourapphasnoreferencestoReactNative,sowe'llstartby
installingit.Intheapp'srootfolder,intheTerminal,installReactNative
fromthecommandlineusingyarn:
yarnaddreact-native
Alternatively,youcanusenpm:
npminstallreact-native--save
2. We'llalsoneedaNode.jsscriptforstartingtheReactNativeapp.Let's
openpackage.jsonandaddthefollowingpropertyasamemberof
thescriptsobject:
"start":"nodenode_modules/react-native/local-cli/cli.jsstart"
3. WeonlyneedaverysimpleReactNativeappforthisrecipe.Let'screate
anindex.android.jsfilewiththefollowingboilerplateapp:
importReact,{Component}from'react';
import{AppRegistry,StyleSheet,View,Text}from'react-native';
exportdefaultclassEmbedAppextendsComponent{
render(){
return(<Viewstyle={styles.container}>
<Text>HelloinReactNative</Text>
</View>);
}
}
conststyles=StyleSheet.create({
container:{
flex:1,
justifyContent:'center',
alignItems:'center',backgroundColor:'#F5FCFF'
}
});
AppRegistry.registerComponent('EmbedApp',()=>EmbedApp);
4. Let'sreturntoAndroidStudioandopenthebuild.gradlefile(from
theappmodule)beforeaddingthefollowingtothedependencies:
dependencies{
implementationfileTree(dir:"libs",include:["*.jar"])
implementation"com.android.support:appcompat-v7:27.1.1"
implementation"com.facebook.react:react-native:+"//Fromnode_modules
}
5. We'llalsoneedareferencetothelocalReactNativemavendirectory.Open
theotherbuild.gradleandaddthefollowinglinetotheallprojects.repositories
object:
allprojects{
repositories{
mavenLocal()
maven{
url"$rootDir/../node_modules/react-native/android"
}
google()
jcenter()
}
}
6. Next,let'supdatetheapp'spermissionstousetheinternet,andthesystem
alterwindow.We'llopenAndroidManifest.xmlandaddthefollowing
permissionstothe<manifest>node:
<?xmlversion="1.0"encoding="utf-8"?>
<manifestxmlns:android="http://schemas.android.com/apk/res/android"
package="com.warlyware.embeddedapp">
<uses-permissionandroid:name="android.permission.INTERNET"/>
<uses-permissionandroid:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<application
android:name=".EmbedApp"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activityandroid:name=".MainActivity">
<intent-filter>
<actionandroid:name="android.intent.action.MAIN"/>
<categoryandroid:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
</manifest>
7. We'rereadytoupdatetheMainApplicationJavaclass.ThegetUseDeveloperSupport
methodherewillenablethedevelopmentmenu.ThegetPackagesmethodis
alistofpackagesusedbytheapp,andonlyincludesMainReactPackage()since
weareonlyusingthemainReactpackage.ThegetJSMainModuleNamemethod
returnstheindex.androidstring,whichreferstotheindex.android.jsfileinthe
ReactNativelayer:
importandroid.app.Application;
importcom.facebook.react.ReactApplication;
importcom.facebook.react.ReactNativeHost;
importcom.facebook.react.ReactPackage;
importcom.facebook.react.shell.MainReactPackage;
importjava.util.Arrays;
importjava.util.List;
publicclassMainApplicationextendsApplicationimplementsReactApplication{
privatefinalReactNativeHostmReactNativeHost=newReactNativeHost(this){
@Override
publicbooleangetUseDeveloperSupport(){
returnBuildConfig.DEBUG;
}
@Override
protectedList<ReactPackage>getPackages(){
returnArrays.<ReactPackage>asList(
newMainReactPackage()
);
}
};
@Override
publicReactNativeHostgetReactNativeHost(){
returnmReactNativeHost;
}
@Override
protectedStringgetJSMainModuleName(){
return"index.android";
}
}
8. Next,let'screateanothernewJavaclasswiththenameReactFragment.This
classneedsthreemethods:OnAttachiscalledwhenthefragmentisattached
tothemainactivity,OnCreateViewinstantiatestheviewforthefragment,and
OnActivityCreatediscalledwhentheactivityisbeingcreated:
importandroid.app.Fragment;
importandroid.content.Context;
importandroid.os.Bundle;
importandroid.view.LayoutInflater;
importandroid.view.ViewGroup;
importcom.facebook.react.ReactInstanceManager;
importcom.facebook.react.ReactRootView;
publicabstractclassReactFragmentextendsFragment{
privateReactRootViewmReactRootView;
privateReactInstanceManagermReactInstanceManager;
//Thismethodreturnsthenameofourtop-levelcomponenttoshow
publicabstractStringgetMainComponentName();
@Override
publicvoidonAttach(Contextcontext){
super.onAttach(context);
mReactRootView=newReactRootView(context);
mReactInstanceManager=
((EmbedApp)getActivity().getApplication())
.getReactNativeHost()
.getReactInstanceManager();
}
@Override
publicReactRootViewonCreateView(LayoutInflaterinflater,ViewGroupgroup,BundlesavedInstanceState){
super.onCreate(savedInstanceState);
returnmReactRootView;
}
@Override
publicvoidonActivityCreated(BundlesavedInstanceState){
super.onActivityCreated(savedInstanceState);
mReactRootView.startReactApplication(
mReactInstanceManager,
getMainComponentName(),
getArguments()
);
}
}
10. Finally,createaJavaclasscalledEmbedFragmentthatwillextendReactFragment:
importandroid.os.Bundle;
publicclassEmbedFragmentextendsReactFragment{
@Override
publicStringgetMainComponentName(){
return"EmbedApp";
}
}
11. Let'sopenMainActivity.javaandaddimplementsDefaultHardwareBackBtnHandlerto
theclassdefinitionforhandlinghardwarebackbuttonevents.Youcanview
theannotatedsourcecodeforthisReactNativeclasshere:https://github.com/
facebook/react-native/blob/master/ReactAndroid/src/main/java/com/facebook/react/modu
les/core/DefaultHardwareBackBtnHandler.java.
12. We'llalsobeaddingafewmethodstotheclass.TheonCreatemethodwillset
thecontentviewtotheMainActivityandaddaFABbuttonthat,when
clicked,willinstantiateanewinstanceoftheEmbedFragmentwedefinedinstep
10.ThatinstanceofEmbedFragmentisusedbythefragmentmanagertoaddthe
ReactNativeapptotheview.Theremainingmethodshandletheeventsthat
occurwhenthedevice'ssystembuttonsarepressed(suchastheback,
pause,andresumebuttons):
importandroid.app.Fragment;
importandroid.os.Bundle;
importandroid.support.design.widget.FloatingActionButton;
importandroid.support.v7.app.AppCompatActivity;
importandroid.support.v7.widget.Toolbar;
importandroid.view.KeyEvent;
importandroid.view.View;
importcom.facebook.react.ReactInstanceManager;
importcom.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
publicclassMainActivityextendsAppCompatActivityimplementsDefaultHardwareBackBtnHandler{
privateReactInstanceManagermReactInstanceManager;
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbartoolbar=(Toolbar)findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
FloatingActionButtonfab=(FloatingActionButton)findViewById(R.id.fab);
fab.setOnClickListener(newView.OnClickListener(){
@Override
publicvoidonClick(Viewview){
FragmentviewFragment=newEmbedFragment();
getFragmentManager().beginTransaction().add(R.id.reactnativeembed,viewFragment).commit();}
});
mReactInstanceManager=((EmbedApp)getApplication()).getReactNativeHost().getReactInstanceManager();
}
@Override
publicvoidinvokeDefaultOnBackPressed(){
super.onBackPressed();
}
@Override
protectedvoidonPause(){
super.onPause();
if(mReactInstanceManager!=null){
mReactInstanceManager.onHostPause(this);
}
}
@Override
protectedvoidonResume(){
super.onResume();
if(mReactInstanceManager!=null){
mReactInstanceManager.onHostResume(this,this);
}
}
@Override
protectedvoidonDestroy(){
super.onDestroy();
if(mReactInstanceManager!=null){
mReactInstanceManager.onHostDestroy(this);
}
}
@Override
publicvoidonBackPressed(){
if(mReactInstanceManager!=null){
mReactInstanceManager.onBackPressed();
}else{
super.onBackPressed();
}
}
@Override
publicbooleanonKeyUp(intkeyCode,KeyEventevent){
if(keyCode==KeyEvent.KEYCODE_MENU&&mReactInstanceManager!=null){
mReactInstanceManager.showDevOptionsDialog();
returntrue;
}
returnsuper.onKeyUp(keyCode,event);
}
}
13. Thelaststepistoaddsomesettingsforthelayoutwhenthefragmentis
loaded.We'llneedtoeditthecontent_main.xmlfile,whichislocatedinthe/res
folder.Thisisthemaincontentoftheview.Itholdsthecontainerview
(FrameLayout)thatwewillattachthefragmentto,andtheothernative
elementsshouldbedisplayed:
<FrameLayout
android:layout_width="match_parent"
android:layout_height="300dp"
android:layout_centerVertical="true"
android:layout_alignParentStart="true"
android:id="@+id/reactnativeembed"
android:background="#FFF">
</FrameLayout>
14. IntheTerminal,runthefollowingcommand:
react-nativestart
ThisbuildsandhoststheReactNativeapp.Now,wecanopentheappin
theAndroidemulator.YouwillseethefollowingafterpressingtheFAB
button:
Howitworks...
ToaccomplishrenderingReactNativeinsideofourAndroidapplication,wehad
toperformafewsteps.First,wehadtodefineanApplicationclassthat
implementstheReactApplicationinterface.Then,wehadtocreateaFragmentthat
wouldberesponsibleforinstantiatingandrenderingtheReactRootView.Witha
fragment,weareabletorendertheReactNativeviewinourMainActivity.Inthis
recipe,weaddedthefragmenttoourfragmentcontainerview.Thisessentially
replacesalloftheapplicationcontentwiththeReactNativeapplication.
Wecoveredalotofintegrationcodeinthisrecipe.Foramorein-depthlookat
howeachofthesepieceswork,Iencourageyoutoreadtheofficial
documentationathttps://facebook.github.io/react-native/docs/integration-with-existing
-apps.html.
CommunicatingfromanAndroidapp
toReactNative
NowthatwehavecoveredhowtorenderourReactNativeappinsidean
AndroidappintheEmbeddingaReactNativeappinsideaNativeAndroid
apprecipe,weneedtotakethattothenextlevel.OurReactNativeapplication
shouldbemorethanadummyUI.Itshouldbeabletoreacttoactionsthatare
goingoninitsparentapplication.
Inthisrecipe,wewillaccomplishsendingdatafromourAndroidapplicationto
ourembeddedReactNativeapp.TheReactNativeapplicationcanacceptdata
whenitisfirstinstantiated,andthenatruntime.Wewillbecoveringhowto
accomplishbothmethods.ThisrecipewilluseEditTextintheAndroidappand
setupone-waybindingtotheReactNativeapp.
Gettingready
Forthisrecipe,pleaseensurethatyouhaveanAndroidappwithaReactNative
appembedded.Ifyouneedguidancetoaccomplishthis,pleasecomplete
theEmbeddingaReactNativeappinsideaNativeAndroidapprecipe.
Howtodoit...
1. OpenAndroidStudioinyourProjectandopencontent_main.xml.
2. PresstheTexttabonthebottomtoopenthesourceeditorandadd/replace
thefollowingnodes:
<TextViewandroid:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="PresstheMailIcontostarttheReactNativeapplication"
android:id="@+id/textView"/>
<FrameLayoutandroid:layout_width="match_parent"
android:layout_height="300dp"
android:layout_centerVertical="true"
android:layout_alignParentStart="true"
android:id="@+id/reactnativeembed"
android:background="#FFF">
</FrameLayout>
<LinearLayoutandroid:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="75dp"
android:layout_below="@+id/textView"
android:layout_centerHorizontal="true">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="UserName:"
android:id="@+id/textView2"
android:layout_weight="0.14"/>
<EditTextandroid:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/userName"
android:layout_weight="0.78"
android:inputType="text"
android:singleLine="true"
android:imeOptions="actionDone"/>
</LinearLayout>
3. OpenMainActivity.javaandaddthefollowingclassfields:
privateReactInstanceManagermReactInstanceManager;
privateEditTextuserNameField;
privateBooleanisRNRunning=false;
4. InsidetheonCreatemethod,settheuserNameFieldpropertywiththefollowing
code:
userNameField=(EditText)findViewById(R.id.userName);
5. ReplaceFloatingActionButtononClickListenerwiththefollowing:
fab.setOnClickListener(newView.OnClickListener(){
@OverridepublicvoidonClick(Viewview){
FragmentviewFragment=newEmbedFragment();
if(userNameField.getText().length()>0){
BundlelaunchOptions=newBundle();
launchOptions.putString("userName",
userNameField.getText().toString());
viewFragment.setArguments(launchOptions);
}
getFragmentManager().beginTransaction().add(R.id.reactnativeembed,viewFragment).commit();
isRNRunning=true;
}
});
6. Next,weneedtoaddaTextChangedListenertoouruserNameFieldin
theonCreatemethod:
userNameField.addTextChangedListener(newTextWatcher(){
@OverridepublicvoidbeforeTextChanged(CharSequences,intstart,intcount,intafter){}
@OverridepublicvoidonTextChanged(CharSequences,intstart,intbefore,intcount){}
@OverridepublicvoidafterTextChanged(Editables){
if(isRNRunning){
sendUserNameChange(s.toString());
}
}
});
7. ThelastchangeweneedtomakeforourActivityistoaddmethodsthatwill
sendtheeventacrosstheReactNativebridge:
privatevoidsendUserNameChange(StringuserName){
WritableMapparams=Arguments.createMap();
params.putString("userName",userName);
sendReactEvent("UserNameChanged",params);
}
privatevoidsendReactEvent(StringeventName,WritableMapparams){
mReactInstanceManager.getCurrentReactContext()
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit(eventName,params);
}
8. Let'sreturntotheJavaScriptlayerandaddthefollowingcodetotheApp
classimplementation:
importReact,{Component}from'react';
import{
AppRegistry,
StyleSheet,
View,
Text,
NativeAppEventEmitter
}from'react-native';
exportdefaultclassEmbedAppextendsComponent<{}>{
componentWillMount(){
this.setState({
userName:this.props.userName
});
NativeAppEventEmitter.addListener('UserNameChanged',(body)=>{
this.setState({userName:body.userName});
});
}
render(){
return(
<Viewstyle={styles.container}>
<Text>Hello{this.state.userName}</Text>
</View>
);
}
}
conststyles=StyleSheet.create({
container:{
flex:1,
justifyContent:'center',
alignItems:'center',
backgroundColor:'#F5FCFF',
},
welcome:{
fontSize:20,
textAlign:'center',
margin:10,
},
instructions:{
textAlign:'center',
color:'#333333',
marginBottom:5,
},
});
AppRegistry.registerComponent('EmbedApp',()=>EmbedApp);
9. Now,ifyouruntheapplication,youcanentertextintheUserNamefield
andstarttheReactNativeapplication:
Howitworks...
Inthisrecipe,werenderedthefragmentasaninlineview.Instep2,weaddedan
emptyFrameLayoutthatwetargetedinstep5torenderthefragment.Thebinding
functionalitywasaccomplishedbyusingtheReactNativebridgevia
RCTDeviceEventEmitter.Thiswasoriginallydesignedtobeusedwithnativemodules,
butaslongasyouhaveaccesstotheReactContextinstance,youcanuseitforany
communicationwiththeReactNativeJavaScriptlayer.
CommunicatingfromReactNativeto
anAndroidappcontainer
Aswediscussedinthepreviousrecipe,itisextremelybeneficialforour
embeddedapplicationtobeawareofwhat'sgoingonaroundit.Weshouldalso
makeaneffortsothatourAndroidparentapplicationcanbeinformed
aboutwhatgoesoninsidetheReactNativeapplication.Theapplicationshould
notonlybeabletoperformbusinesslogic–itshouldbeabletoupdateitsUIto
reflectchangesintheembeddedapp.
ThisrecipeshowsushowtoleveragenativemodulestoupdatethenativeUI
that'screatedinsidetheAndroidapplication.Wewillhaveatextfieldinour
ReactNativeappthatupdatesatextfieldthatisrenderedinthehostAndroid
application.
Gettingready
Forthisrecipe,pleaseensurethatyouhaveanAndroidapplicationwithaReact
Nativeappembedded.Ifyouneedguidancetoaccomplishthis,pleasecomplete
theEmbeddingaReactNativeappinsideaNativeAndroidapprecipe.
Howtodoit...
1. OpenAndroidStudiotoyourProjectandopencontent_main.xml.
2. PresstheTexttabonthebottomtoopenthesourceeditorandadd/replace
thefollowingnodes:
<?xmlversion="1.0"encoding="utf-8"?>
<RelativeLayoutxmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:context="com.embedapp.MainActivity"
tools:showIn="@layout/activity_main">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="PresstheMailIcontostarttheReactNativeapplication"
android:id="@+id/textView"/>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="300dp"
android:layout_centerVertical="true"
android:layout_alignParentStart="true"
android:id="@+id/reactnativeembed"
android:background="#FFF"></FrameLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="75dp"
android:layout_below="@+id/textView"
android:layout_centerHorizontal="true">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="UserName:"
android:id="@+id/textView2"
android:layout_weight="0.14"/>
<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/userName"
android:layout_weight="0.78"
android:inputType="text"
android:singleLine="true"
android:imeOptions="actionDone"/>
</LinearLayout>
</RelativeLayout>
3. CreateaJavaclassnamedUserNameManager.Thiswillbeanativemodulethat
willservethepurposeofupdatingtheEditTextfieldweaddedtothelayout.
IfyouarenotfamiliarwithcreatinganativemoduleforReactNative,pleasereferto
theExposingcustomAndroidmodulesrecipeinChapter11,AddingNativeFunctionality.
4. MostoftheworkinUserNameManager.javaisbeingdoneinthesetUserName
method.Here,theAndroidlayerupdatesthetextcontentsoftheviewbased
onwhatit'ssentfromtheReactNativelayer.TheReactmethodisn't
necessarilygoingtorunonthemainUIthread,sowe
usemainActivity.runOnUiThreadtoupdatetheviewwhenthemainUIthreadis
ready:
publicclassUserNameManagerextendsReactContextBaseJavaModule{
publicUserNameManager(ReactApplicationContextreactApplicationContext){
super(reactApplicationContext);
}
@OverridepublicStringgetName(){
return"UserNameManager";
}
@ReactMethodpublicvoidsetUserName(finalStringuserName){
ActivitymainActivity=getReactApplicationContext().getCurrentActivity();
finalEditTextuserNameField=(EditText)mainActivity.findViewById(R.id.userName);
mainActivity.runOnUiThread(newRunnable(){
@Overridepublicvoidrun(){
userNameField.setText(userName);
}
});
}
}
5. ToexporttheUserNameManagermodule,we'llneedtoedittheUserNamePackage
Javaclass.WecanexportittotheReactNativelayerbycallingmodules.add,
passinginanewUserNameManagerthattakesthereactContextasaparameter:
publicclassUserNamePackageimplementsReactPackage{
@OverridepublicList<Class<<?extendsJavaScriptModule>>createJSModules(){
returnCollections.emptyList();
}
@OverridepublicList<ViewManager>createViewManagers(ReactApplicationContextreactContext){
returnCollections.emptyList();
}
@OverridepublicList<NativeModule>createNativeModules(ReactApplicationContextreactContext){
List<NativeModule>modules=newArrayList<>();
modules.add(newUserNameManager(reactContext));
returnmodules;
}
}
6. AddtheUserNamePackageinthegetPackagesmethodinMainApplication:
@Override
protectedList<ReactPackage>getPackages(){
returnArrays.<ReactPackage>asList(
newMainReactPackage(),
newUserNamePackage()
);
}
7. Now,weneedtohaveourReactNativeUIrenderaTextFieldandcall
ourUserNameManagernativemodule.Openindex.android.jsandimport
theTextInputandNativeModulesmodulesfrom'react-native'.
8. CreateavariablereferencefortheUserNameManager:
constUserNameManager=NativeModules.UserNameManager;
9. TheReactNativeappwillsimplyneedaTextInputformanipulatinga
userNamepropertyonthestateobject:
letstate={
userName:''
}
onUserNameChange=(userName)=>{
this.setState({
userName
});
UserNameManager.setUserName(userName);
}
render(){
return(
<Viewstyle={styles.container}>
<Text>EmbeddedRNApp</Text>
<Text>EnterUserName</Text>
<TextInputstyle={styles.userNameField}
onChangeText={this.onUserNameChange}
value={this.state.userName}
/>
</View>
);
}
10. Afterrunningtheapplication,startingtheReactNativeembeddedapp,and
addingtexttothetextfield,youshouldseesomethingsimilartowhat's
showninthefollowingscreenshot:
Howitworks...
TogetourReactNativeapptoupdatethenativeappcontainers,wecreateda
nativemodule.ThisistherecommendedwayofcommunicatingfromJavaScript
tothenativelayer.However,sincewehadtoupdateanativeUIcomponent,the
operationhadtobeperformedonthemainthread.Thisisachievedbygettinga
referencetoMainActivityandcallingtherunOnUiThreadmethod.Thisisdonein
thesetUserNamemethodofstep4.
Handlingbeinginvokedbyan
externalAndroidapp
Earlierinthischapter,wecoveredhowtohandleinvocationfromanexternal
appiniOSintheHandlingbeinginvokedbyanexternaliOSapprecipe.Inthis
recipe,we'llcoverthesameconceptofdeeplinkinginAndroid.
Howtodoit...
1. Let'sbeginbyopeningtheReactNativeAndroidprojectinAndroidStudio
andnavigatingtoAndroidManifest.xml.
2. Forourexample,wewillregisterourapplicationunderinvoked://scheme.
We'llupdatethe<activity>nodetothefollowing:
<activity
android:name=".MainActivity"
android:label="@string/app_name"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
android:windowSoftInputMode="adjustResize"
android:launchMode="singleTask">
<intent-filter>
<actionandroid:name="android.intent.action.MAIN"/>
<categoryandroid:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
Formoreinformationonhowthisintent-filterworks,refertotheofficialAndroid
documentationathttps://developer.android.com/training/app-links/deep-linking.
3. Next,we'llneedtocreateasimpleReactNativeappwhoseUIreactsto
beinginvoked.Let'sopentheindex.android.jsfile.We'llstartby
importingtheLinkingmoduleintheimportblockfrom'react-native':
importReactfrom'react';
import{Platform,Text,Linking}from'react-native';
4. Let'sbuildouttheAppclassfortheReactNativeapp.Whenthecomponent
mounts,we'llregisteraLinkingeventlistenerwithaneventwe'llnameurl.
Whenthiseventoccurs,onAppInvokedwillbefired,updatingthestatus
propertyofstate,alongwiththeeventthat'spassedtothecallback:
exportdefaultclassAppextendsReact.Component{
state={
status:'AppRunning'
}

componentWillMount(){
Linking.addEventListener('url',this.onAppInvoked);
}

componentWillUnmount(){
Linking.removeEventListener('url',this.onAppInvoked);
}

onAppInvoked=(event)=>{
this.setState({status:`AppInvokedby${event.url}`});
}

render(){
return(
<Viewstyle={styles.container}>
<Textstyle={styles.instructions}>
AppStatus:
</Text>
<Textstyle={styles.welcome}>
{this.state.status}
</Text>
</View>
);
}
}
5. Runningtheapplicationandinvokingitfromanotherappwilllook
somethinglikethis:
Howitworks...
Inthisrecipe,weregisteredourURLschemaforlinkingbyediting
theAndroidManifest.xmlfileinstep2.Animportantthingtonoteisthechangeof
thelaunchModetosingleTask.Thispreventstheoperatingsystemfromcreating
multipleinstancesofourReactactivity.Thisisimportantifyouwanttobeable
toproperlycapturethedatathat'spassedalongwiththeintent.
DeployingYourApp
Inthischapter,wewillcoverthefollowingrecipes:
DeployingdevelopmentbuildstoaniOSdevice
DeployingdevelopmentbuildstoanAndroiddevice
DeployingtestbuildstoHockeyApp
DeployingiOStestbuildstoTestFlight
DeployingproductionbuildstotheAppleAppStore
DeployingproductionbuildstotheGooglePlayStore
DeployingOver-The-Airupdates
OptimizingReactNativeappsize
Introduction
Ifyou'reanindependentdeveloper,you'relikelytogothroughafewdifferent
stagesofdevelopment.Thefirststagewillfindyoutestingyourapponyour
personaliOSorAndroiddevice.Afterexhaustingthisstage,you'reprobably
goingtowanttoshareitwithaselectgroupofpeopletogetuserfeedback.
Eventually,you'regoingtoreachapointwhereyourappisreadytoberelease
intotheworldviaappstores.Thischapterwillwalkthrougheachoneofthese
stagesandcoverpushingupdatestoyourapp,alongwithafewoptimization
tips.
Deployingdevelopmentbuildstoan
iOSdevice
Duringdevelopment,you'lllikelyspendmuchofyourtimetestingyouriOSapp
usingtheiOSSimulatorthatcomesinstalledwithXcode.WhiletheiOS
Simulatorisbyfarthebestperformingandclosestmethodtorunningour
applicationonaniOSdevice,it'sstillnotthesameastherealthing.TheiOS
Simulatorusesthecomputer'sCPUandGPUtorenderthesimulatedOS,so
dependingonyourdevelopmentmachine,itmayendupperformingbetter(or
worse)thantheactualdevice.
Thankfully,Expo'sabilitytotestrunningcodeonanactualdevicecomesone
stepclosertotherealendproduct,buttherearestilldifferencesbetweenafinal
appandadevelopmentapprunninginExpo.Andifyou'rebuildingapureReact
Nativeapp,youwon'thavetheluxuryofusingExpotoeasilyruntheappona
device.
Eitherway,you'lleventuallywanttotesttherealapponaphysicaldevicesoyou
canexperiencetheactualUXandperformanceoftheendproduct.
Inthisrecipe,wewillwalkyouthroughtakingaReactNativeappand
deployingittoaniPhoneoriPad.
Gettingready
We'lljustneedanewpureReactNativeapp,whichwe'llnameTestDeployApp.You
cancreatetheappviathefollowingcommand:
react-nativeinit
Also,makesureyouriOSdeviceisconnectedtoyourdevelopmentmachinevia
USB.
Howtodoit...
1. Let'sfirstopenthenewlycreatedReactNativeiOSprojectinXcode.Open
theProjectEditorbyselectingtherootoftheprojectintheleftpanel.
2. UndertheGeneraltaboftheProjectEditor,selecttheiOSappinthe
TARGETSsectionontheleft.UndertheSigningsection,selectyourTeam,
asfollows:
3. RepeatthisstepfortwoeachoftheentriesintheTARGETSlist.
4. Selectyourdeviceinthedestinationselector,asfollows:
5. Tostartrunningtheapponyourconnecteddevice,justpressthePlay
button.You'llhavetomakesureyourdeviceispluggedin,unlocked,and
trustedforittoshowupinthedeviceslistinXcode.Ifthisisthefirsttime
runninganappyou'vedevelopedonthisdevice,you'llalsoneedtoadjust
thesettingstotrustappsfromyourdeveloperaccount.OntheiOSdevice,
thissettingcanbefoundinSettings|General|DeviceManagement.
Howitworks...
Deployingourdevelopmentbuildtothedevicesimplyinvolvesdesignatinga
Team,thenrunningtheappasyouwouldforuseontheXcodesimulator,but
targetingthepluggedindeviceinstead.Weusethelocalhostpackagertocreate
ourbundlefile.Thisfilethengetssavedlocallyonthedeviceforthefuture.
Notethat,sincethisisadevelopmentbuild,thecodeisnotyetasoptimizedasit
willbeinafinalrelease.Youwillseeasignificantperformanceincreasewhen
movingtoaproductionrelease.
Deployingdevelopmentbuildstoan
Androiddevice
WhiledevelopinganAndroidapplication,you'llmostoftenprobablyberunning
theapponanAndroidemulatorsuchasGenymotion.Whileconvenient,an
emulatorwillhavepoorperformancewhencomparedwitharealAndroid
device.
ThebestwaytotestanappistouseaphysicalAndroiddevice.Thisrecipewill
walkthroughdeployingaReactNativeapptoaphysicalAndroiddevice.
Gettingready
We'lljustneedanewpureReactNativeapp,whichwe'llnameTestDeployApp.You
cancreatetheappviathiscommand:
react-nativeinit
Also,makesureyouriOSdeviceisconnectedtoyourdevelopmentmachinevia
USB.
Howtodoit...
1. Let'sstartbyopeningourReactNativeAndroidprojectinAndroidStudio.
2. Next,presstherunbutton,asfollows:
3. MakesuretheChoosearunningdeviceradiobuttonisselected,andthat
yourdeviceisdisplayedinthelist.PressOKtocontinue,asfollows:
There'smore...
TheReactNativepackagershouldstartwhenyouruntheapplication.Ifit
doesn't,you'llhavetomanuallystartthepackager.Ifyouseeanerrorscreen
withthemessageCouldnotgetBatchedBridge,pleasemakesureyourbundleis
packagedcorrectlyorCouldnotconnecttodevelopmentserver,youshouldbe
abletofixthisbyrunningthefollowingcommandintheTerminal:
adbreversetcp:8081tcp:8081
Howitworks...
MuchlikeXcode,wecanrunourappbysimplyplugginginarealdevice,
pressingRun,andselectingthedevicetheappshouldrunon.Theonly
complicationthatmightariseissettingupcommunicationbetweenthedevice
andthedevelopmentmachine.Runthiscommand:
adbreverse
Thisestablishesaportforwardfromthedevicetothehostcomputer.Thisisa
developmentbuild,andthecodeisnotyetoptimized,sotherewillbea
performanceincreaseoncetheappisbuiltasaproductionrelease.
DeployingtestbuildstoHockeyApp
Beforereleasinganappintothewild,it'simportanttostresstestyourappandto
getuserfeedbackwhenpossible.Toaccomplishthis,youneedtocreateasigned
buildofyourappthatyoucansharewithagroupoftestusers.Forarobusttest
build,you'llneedtwothings:analytics/reportingonappperformance,and
amechanismfordelivery.HockeyAppprovidesthisandmoreforyourtest
buildsonbothiOSandAndroid.
ThisrecipewillwalkthroughdeployingaReactNativeapptoHockeyAppfor
testingpurposes.WewillwalkthroughbothiOSandAndroidreleases.
Gettingready
Forthisrecipe,wewillbeusingthesameempty,pureReactNativeappfromthe
lasttworecipes,whichwenamedTestDeployApp.ForiOSdeployments,youwill
needtobeenrolledintheAppleDeveloperProgram,andyou'llneedtohave
cocoapodsinstalled.Theeasiestwaytoinstallcocoapodsistousehomebrew,viathis
command:
brewinstallcocoapods
You'llalsoneedtohaveaHockeyAppaccount,whichyoucansignupforat
theirwebsiteat:
https://hockeyapp.net/
Howtodoit...
1. First,weneedtoinstallthereact-native-hockeyappmoduleinourapplication.
OpentheTerminal,gotoyourapplication'srootprojectdirectory,andenter
thefollowingcommand:
$npminstallreact-native-hockeyapp--save
2. Gointoyourios/directoryandinitializeyourPodfile:
$podinit
3. OpenyourPodfileandaddpod"HockeySDK"toyourtarget.
4. BackintheTerminal,installthePodfile,asfollows:
$podinstall
5. Now,let'sopenupXcodeandopenourReactNativeproject:
(ios/TestDeployApp.xcodeproj).
6. IrecommendchangingyourBundleIdentifiertosomethingmore
meaningfulthanthedefault,sopleasechangeitinyourGeneralSettings
dialog,asfollows:
7. Draganddrop./ios/Pods/Pods.xcodeprojintotheLibrariesgroupinyour
projectnavigator,asfollows:
8. DraganddroptheRNHockeyApp.handRNHockeyApp.mfileslocatedin
./node_modules/react-native-hockeyapp/RNHockeyApp/RNHockeyAppintothesame
Librariesgroup.
9. Next,we'llgototheHockeyAppsiteandcreateourappthere.Loginand
clicktheNewApp.
10. Sincewedonothaveourbuildreadyyet,clickmanuallyinthephrase
Don'twanttouploadabuild?Createtheappmanuallyinsteadinthe
followingmodal.
11. WhenfillingoutthefieldsintheCreateAppform,besuretomatchthe
TitleandBundleIdentifierthatwedefinedearlierinstep6,thenpressSave,
asfollows:
12. MakeanoteoftheAppIDsincewe'llbeusingitinthenextstep.
13. OpenApp.jsandaddthefollowingcode:
importHockeyAppfrom'react-native-hockeyapp';
exportdefaultclass
TestDeployAppextendsComponent{
componentWillMount(){
HockeyApp.configure(YOUR_APP_ID_HERE,true);
}
componentDidMount(){
HockeyApp.start();
HockeyApp.checkForUpdate();
}
}
14. BackinXcode,setGenericiOSDeviceasyourdestinationtargetandbuild
(Product|Build)theapp,asfollows:
15. Now,weneedtocreateour.ipafile.ThiscanbedonefromtheXcodemenu
viaProduct|Archive.
16. ThiswillopentheArchiveslist.PresstheDistributeAppbuttontostartthe
processofcreatingthe.ipa.
17. SelecttheDevelopmentoptionandpressNext.
18. Yourprovisioningteamshouldautomaticallybeselected.Withthecorrect
Teamselected,pressNext.
19. LeavethedefaultExportsettingsandpressNext.Onthesummarypage,
alsopressNext.
20. SelectthedestinationdirectoryandpressExport.
21. BackintheHockeyAppbrowserwindow,clickAddVersion.
22. Dragthe.ipafilewejustexportedintothemodalwindow.
23. Wecanleavethesettingsheresettotheirdefaults,socontinuepressing
Nextuntilthelastmodalscreen,thenpressDoneatthesummaryscreen.
That'sitfortheiOSapp.YoucanadduserstoyourHockeyAppapp,and
yourtestersshouldthenbeabletodownloadyourapp.Let'sswitchoverto
theAndroidsideofthings.OpenAndroidStudio,thenopentheAndroid
folderinourReactNativeprojectat:
https://support.hockeyapp.net/kb/client-integration-android/hockeyapp-for-android-sdk
24. Repeatstep8tostep11,changingthePlatformtoAndroid,asfollows:
25. Now,weneedtobuildour.apkfile.Youcanfindthemostup-to-date
methodforbuildingthe.apkintheReactNativedocumentation,locatedat:
https://facebook.github.io/react-native/docs/signed-apk-android.html
26. Repeatstep21andstep22forthe.apkgeneratedfromourAndroidproject.
Howitworks...
Forthisrecipe,weusedHockeyAppforitstwomainfeatures:itsbeta
distributionanditsHockeySDK(whichsupportscrashreporting,metrics,
feedback,authentication,andnotificationsforupdates).ForiOS,beta
distributionisdonethroughtheOTAenterprisedistributionmechanismhosted
byHockeyApp.Whenyousignyourapp,youcontrolwhichdevicescanopenit.
HockeyAppjustsendsnotificationsandprovidestheURLforbetatesters
todownloadyourappthroughitsenterpriseappstore.Androidissimplersince
thereisnoneedtoworryabouthowappsaretransferred.Thismeans
HockeyApphoststhe.apkfileonawebserverthattesterscandownloadand
install.
DeployingiOStestbuildsto
TestFlight
BeforeHockeyAppcamealong,theserviceforbetatestingmobileappswas
TestFlight.Infact,itwassogoodatdoingjustthat,thatApplepurchasedits
parentcompanyandintegrateditintoiTunesConnect.TestFlightnowservesas
theofficialapptestingplatformforApple.Thereareafewdifferencesbetween
TestFlightandHockeyApptoconsider.Firstandforemost,TestFlightbecame
iOSonlywhenitwaspurchasedbyApple.Second,therearetwostylesof
testinginTestFlight:internalandexternal.Internaltestinginvolvessharingthe
applicationwithDeveloperorAdminrolemembersofyourteam,andlimits
distributionto25testersacross10deviceseach.Externaltestingallowsyouto
inviteupto2,000testerswhodonothavetobemembersofyourorganization.
Thisalsomeansthatthesetestersdonotuseupyourdevicequota.External
testingapplicationsgothroughtheBetaAppReviewperformedbyApple,
whichisnotquiteasrigorousasApple'sreviewforreleasinganapptotheApp
Store,butitisagoodfirstpass.
ThisrecipefocusesontakingourReactNativeappanddeployingatestbuildto
TestFlight.Wewillbesettingupaninternaltest,sincewedonotwantApple
reviewingourexampleReactNativeapp,buttheprocedureisthesameforboth
internalandexternaltesting.
Gettingready
Forthisrecipe,wewillbeusingthesameboilerplateReactNativeappfrom
previousrecipes,whichwe'venamedTestDeployApp.Youwillalsoneedtobe
enrolledintheAppleDeveloperProgram,you'llneedtohaveyourdevelopment
anddistributioncertificatessetupinXcode,andyourappwillneedtohaveits
AppIconset.
Howtodoit...
1. Let'sstartbyopeningourprojectinXcodevia
theios/TestDeployApp.xcodeprojfile.
2. Asstatedinthelastrecipe,Ialsorecommendchangingyour
BundleIdentifiertosomethingmoremeaningfulthanthedefault,for
example:
3. Next,let'slogintotheAppleDeveloperProgramandnavigatetotheApp
IDregistrationpage,locatedathttps//:developer.apple.com/account/ios/identifie
r/bundle.
4. Here,fillouttheNameandBundleIDforyourproject,thenpress
theContinuebutton,followedbytheRegisterbutton,andfinallytheDone
buttontocompleteregistrationoftheapp.
5. Next,we'lllogintotheiTunesConnectsite,locatedathttps://itunesconnect.a
pple.com.
6. IniTunesConnect,navigatetoMyApps,thenpressthePlus(+)buttonand
selectNewApptoaddanewapp.
7. IntheNewAppdialog,fillouttheNameandLanguage.Selectthe
BundleIDtomatchtheoneyoucreatedpreviously,andaddauniqueapp
referenceintheSKUfield,thenpressCreate.
8. Next,navigatetotheTestFlightsectionforyourappandbesuretofillout
theLocalizableInformationsection.
9. Let'sreturntoXcodetocreatethe.ipafile.SelectGenericiOSDevicefor
theactivescheme,thencreatethefileviatheXcodemenu
(Product|Archive).ThiswillopentheArchiveslist,whereyoucanpress
theUploadtoAppStorebuttontouploadtheapp.
10. Yourprovisioningteamshouldautomaticallybeselected.Besurethe
correctteamisselectedandpressChoose.Oncethearchiveiscreated,press
theUploadbutton.
11. Afteruploadingtheapp,you'llneedtowaituntilyoureceiveanemailfrom
iTunesConnectinformingyouthatthebuildhascompletedprocessing.
Onceprocessingiscomplete,youcanreturntotheiTunesConnectpage
andopentheInternalTestingview.
12. IntheInternalTestingsection,clickSelectVersiontoTestandselectyour
build,thenclicktheNextbutton.AttheExportCompliancescreen,press
OK.
13. We'rereadytoaddinternaltesters.Selecttheusersyouwouldliketotest
theapp,thenclicktheStartTestingbuttonandconfirmyourselectioninthe
followingmodal.Yourusersshouldnowgetaninvitationemailtotestyour
app!
Howitworks...
TestFlightservesasafirst-classcitizenintheAppStorepublishingpipeline.
Applehasintegrateditssupportforapplicationbetatestingdistributiondirectly
intoiTunesConnect,creatingasmoothandseamlessprocessfordevelopers.
ThisprocedureislargelythesameasdeployingtotheAppStore,exceptthat
whenusingiTunesConnect,youmustenableandconfiguretesting.
Itisaseamlessexperienceforthetesteraswell.Assoonasyouaddtestusersin
iTunesConnect,theyarenotifiedtoinstalltheTestFlightapp,wherethey
willhaveeasyaccesstotheappstheycantest.TestFlightalsomakestheprocess
easierfordevelopersbynotrequiringthemtoaddanyextrathird-partylibraries
orcodetosupportTestFlight,aswouldbeneededwithHockeyApp.
Deployingproductionbuildstothe
AppleAppStore
Onceyou'vethoroughlytestedyourapp,you'rereadytomoveontothenext
(andlikelythemostexciting)stepintheiOSappmakingprocess:releasingto
theAppleAppStore.
Thisrecipewillwalkthroughtheprocessofpreparingyourproductionbuildand
submittingittotheAppleAppStore.Wewon'tactuallybesubmittingtheappto
thestore,sincewe'reworkingwithanexampleappinsteadofaproduction-ready
one.Thelastfewstepsintheprocess,however,areverystraightforward.
Gettingready
Forthisrecipe,wewillagainbeusingthesimpleReactNativeexampleapp
fromearlierrecipes,TestDeployApp.You'llofcoursealsoneedtobeenrolledinthe
AppleDeveloperProgram,andhaveyourdevelopmentanddistribution
certificatessetupinXcodeasdiscussedearlierinthischapter.Forareal
productionappdeployment,youwillalsoneedtohaveboththeAppIconsetand
screenshotsoftheappreadyforuseiniTunes.
Howtodoit...
1. Let'sstartbyopeningupXcodeusingtheios/TestDeployApp.xcodeprojfile.
2. Asstatedbefore,it'srecommendedthatyouchangeyourBundleIdentifier
tosomethingmoremeaningfulthanthedefault,sobesuretochangeitin
theGeneralSettingsdialog.
3. It'salsoagoodideatotestyourappinProductionModeonyourdevice.
Thiscanbedonebychangingyourappscheme'sBuildConfiguration
(foundviatheProduct|Scheme|EditSchememenus)toRelease,as
follows:
4. Next,you'llneedtoregistertheappontheAppIDregistrationpage,
locatedat:
https://developer.apple.com/account/ios/identifier/bundle
ThissteprequiresanactiveAppleDeveloperProgramaccount.
5. FillouttheNameandBundleIDfieldsforyourprojectandpress
theContinuebutton.
6. Next,we'lllogintotheiTunesConnectsite,locatedathttps://itunesconnect.a
pple.com.IntheMyAppssection,pressthePlus(+)buttonandselectNew
App.
7. You'llneedtofillouttheNameandLanguageinthefollowingdialog,then
selecttheBundleIDmatchingtheoneyoucreatedearlierintherecipe.
Also,addauniqueappreferencefortheSKUandpresstheCreatebutton.
8. Let'sreturntoXcodeandcreatethe.ipafile.SelectGenericiOSDevicefor
theactivescheme,andcreatethefileviathemenus(Product|Archive),
whichwillopentheArchiveslist.Finally,pressUploadtoAppStore.
9. SelectyourProvisioningTeam,thenpressChoose.
10. Oncethearchivehasbeencreated,presstheUploadbutton.Oncethebuild
hasbeenprocessed,you'llreceiveanemailfromiTunesConnect.
11. Oncetheappisprocessed,returntoiTunesConnect.UndertheAppStore
section,openAppInformationandselectthecategorythatyourappfits
into.
12. Openthe1.0PrepareforSubmissionsectionunderiOSAPP.Filloutallthe
requiredfields,includingAppScreenshots,Description,Keywords,and
SupportURL.
13. Next,undertheBuildsection,selectthe.ipawebuiltinstep8.
14. Finally,fillouttheCopyrightandAppReviewInformationsections,then
clicktheSubmitforReviewbutton.
Howitworks...
Inthisrecipe,wecoveredthestandardprocessforpublishingiOSappstothe
AppStore.TherearenoReactNative-specificstepsweneededtofollowinthis
case,sincethefinalproduct(the.ipafile)containsallofthecodeneededtorun
theReactNativepackager,whichwillinturnbuildthemain.jsbundlefileinrelease
mode.
Deployingproductionbuildsto
GooglePlayStore
Thisrecipewillwalkthroughtheprocessofpreparingaproductionbuildofour
appandsubmittingittotheGooglePlayStore.Asinthelastrecipe,we'llstop
rightbeforeactuallysubmittingtotheAppStore,sincethisisonlyanexample
ReactNativeapp,buttherestofthisprocessisalsostraightforward.
Gettingready
Forthisrecipe,wewillbeusingthesamesimpleReactNativeappwe'veused
throughoutthischapter,TestDeployApp.YouwillneedtohaveaGooglePlay
Developeraccountinordertosubmitanapptothestore,andyou'llalsoneedto
havealltheiconsandscreenshotsreadyforthePlayStoreifyouwant
toactuallypublishyourapp.
Howtodoit...
1. Let'sstartbyopeningtheReactNativeprojectinAndroidStudio.Thefirst
stepisbuildingthe.apkfile.Asmentionedearlierinthischapter,the
processofcreatingaproductionAndroidappfromaReactNativeprojectis
involvedandpronetochange.VisittheReactNativeDocumentationfor
creatingthe.apkathttps://facebook.github.io/react-native/docs/signed-apk-android
.html.
2. Next,let'sopentheGooglePlayDeveloperConsoleinawebbrowser,
locatedathttps://play.google.com/apps/publish/.
3. Let'skickofftheprocessbyclickingAddnewapplication.Filloutthe
Titlefield,andclicktheUploadAPKbutton,asfollows:
4. You'llseetheAPKsectionofthePublishscreennext.ClickUploadyour
firstAPKtoProduction,thendraganddrop(orselect)your.apkfile.
5. Aseriesofself-explanatorymodalswillfollow.GothroughtheStore
Listing,ContentRating,Pricing,andDistributionsectionsandfilloutallof
theinformationaccordingly.
6. Onceyouhavesatisfiedalltherequirements,pressthePublishAppbutton.
Howitworks...
Inthisrecipe,wecoveredtheprocessforpublishingAndroidappstotheGoogle
PlayStore.Byfollowingthedirectionslinkedtoinstep2,yourReactNativeapp
willhavebeenthroughtheGradleassembleReleaseprocess.Theassembleprocess
runsthepackagertocreatetheJavaScriptbundlefile,compiletheJavaclasses,
packagethemtogetherwiththeappropriateresources,andfinallyallowyouto
signtheappintoan.apk.
DeployingOver-The-Airupdates
OneusefulsideeffectofourReactNativeappbeingwritteninJavaScriptisthat
thecodeisloadedatruntime,whichissimilartohowCordovahybrid
applicationswork.Wecanleveragethisfunctionalitytopushupdatestoour
applicationusingOver-The-Air(OTA).Thisallowsforaddingfeaturesandbug
fixeswithouthavingtogothroughtheAppStoreapprovalprocess.Theonly
limitationtoOTAupdatesforReactNativeisthatwecannotpushcompiled
(Objective-CorJava)code,whichmeanstheupdatecodemustbeinthe
JavaScriptlayeronly.Thereareafewpopularservicesthatprovidecloud-based
OTAappupdates.WewillbehighlightingCodePush,aservicebyMicrosoft.
ThisrecipewillcoversettingupandpushingupdatesusingCodePushforourReact
NativeapponbothiOSandAndroid.
Gettingready
Forthisrecipe,wewillbeusingthesamesimpleReactNativeappwe'veused
throughoutthischapter,TestDeployApp.We'llbedeployingtheappstophysical
devicesrunninginproduction/releasemode,whichwillallowtheapptoreceive
updatesfromtheCodePushservers.
Howtodoit...
1. InordertouseCodePush,wewillneedtoinstalltheCodePushCLIand
createafreeaccount.ThiscanbedoneinaTerminalbyrunningthe
followingtwocommands:
npminstall-gcode-push-cli
code-pushregister
2. ThenextstepistoregisterourappwithCodePush.Makeanoteofthe
deploymentkeysfortheappprovidedbytheoutputfromrunningcode-push
register.Wewillbeusingthestagingkeyforthisrecipe.The
documentationsuggestsaddingoneappperplatform,withan-IOSor-
Androidsuffixforeach.ToaddtheapptoCodePush,usethiscommand:
code-pushappaddTestDeployApp-IOS
code-pushappaddTestDeployApp-Android
3. We'realsogoingtoneedtheReactNativeCodePushmoduleinstalledinthe
ReactNativeprojectdirectory.Thiscanbedonewithnpm,asfollows:
npminstall--savereact-native-code-push
Or,wecanuseyarn,forexample:
yarnaddreact-native-code-push
4. ThenextstepislinkingtheCodePushnativemoduleswithourproject.
WhenpromptedforyourdeploymentkeyforAndroidandiOS,usethe
stagingkeydiscussedinstep2.Linkingthenativemodulescanbedone
withthefollowingcommand:
react-nativelinkreact-native-code-push
5. Next,weneedtosetourReactNativeappuptouseCodePush.Inside
ofindex.js,we'llneedtoaddthreethings:theCodePushimport,anoptions
object,andacalltotheimportedcodePushmodulewhenregisteringtheapp
viaAppRegistry.registerComponent.Setuptheappasfollows:
import{AppRegistry}from'react-native';
importAppfrom'./App';
importcodePushfrom'react-native-code-push';
constcodePushOptions={
updateDialog:true
}
AppRegistry.registerComponent('TestDeployApp',
()=>codePush(codePushOptions)(App)
)
6. TotestoutourchangesintheiOSapp,let'sdeploytoouriOSdevice.Open
theReactNativeprojectinXcode,changeyourscheme'sBuild
Configuration(Product|Scheme|EditScheme...)toRelease,thenpress
Run,asfollows:
7. Next,makesomesortofarbitrarychangetotheReactNativecodeinthe
app,thenintheTerminal,runthefollowingcommandtoupdatetheapp
withthenewcode:
code-pushrelease-reactTestDeployAppios-m--description"UpdatingusingCodePush"
8. Next,closeandreopentheapponyouriOSdevice.Youshouldseethe
followingprompt:
9. Aftercontinuingpasttheprompt,theappwillupdateitselftothelatest
version!
10. Let'salsotestthefeatureonAndroid.You'llneedtohavemadeyour
Androidappintoa.apkfilebyfollowingthestepsoutlinedintheReact
Nativedocumentationathttps://facebook.github.io/react-native/docs/signed-apk-
android.html.
11. WithyourAndroiddevicepluggedintoyourdevelopmentmachine,runthe
followingcommandintheTerminalfromtheandroid/directory:
adbinstall
app/build/outputs/apk/app-release.apk
12. Next,makechangetotheReactNativeJavaScriptcode.Aslongasnew
codeisadded,wecanusethatchangedcodetoupdatetheapp.Then,run
thefollowingcommandintheTerminal:
code-pushrelease-reactTestDeployAppandroid-m--description"UpdatingusingCodePush"
13. Onceagain,closeandreopenyourapponyourAndroiddevicetogetthe
followingprompt:
14. Afterproceedingpasttheprompt,theappwillupdateitselftothelatest
version.
Howitworks...
CodePush(aswellasothercloud-hostedOTAupdateplatforms)worksbyusing
thesametechniquethathasexistedinReactNativesinceitsinception.React
NativeloadsaJavaScriptbundlewhentheappisinitialized.During
development,thisbundleisloadedfromlocalhost:3000.Oncewe'vedeployedan
app,however,itwilllookforafilenamedmain.jsbundlethathasbeenincludedin
thefinalproduct.ByaddingthecalltocodePushinregisterComponentinstep5,the
appwillcheckinwiththeCodePushAPItoseeifthereisanupdate.Ifthereisa
newupdate,itwillprompttheuseraboutit.Acceptingthepromptdownloads
thenewjsbundlefileandrestartstheapp,causingthecodetobeupdated.
OptimizingReactNativeappsize
Beforedeployingourapptoproduction,it'salwaysagoodideatoshrinktheapp
bundlesizetoassmallafileaspossible,andthereareseveraltechniqueswecan
leveragetodoso.Thesecaninvolvesupportingfewerdevicesorcompressing
includedassets.
Thisrecipewillcoverafewtechniquesforlimitingproductionpackagefilesizes
inbothiOSandAndroidReactNativeapps.
Gettingready
Forthisrecipe,wewillbeusingthesamesimpleReactNativeappwe'veused
throughoutthischapter,TestDeployApp.You'llalsoneedtohavecodesigning
workingforiOS,andtheabilitytocreate.apkfilesascoveredinprevious
recipes.
Howtodoit...
1. Wewillstartoffwithsomesimpleoptimizationsperformedonourbundled
assets,whichoftenincludesimagesandexternalfonts:
ForPNGandJPEGcompression,youcanuseaservicesuchashttp://w
ww.tinypng.comtoreducethefilesizewithlittletonoreductioninimage
quality.
Ifyouusethereact-native-vector-iconslibrary,youwillnoticethatit
bundleseightdifferentfonticonsets.It'srecommendedthatyou
removeanyoftheiconfontlibrariesthatarenotbeingusedbyyour
app.
SVGfilescanalsobecompressedandoptimized.Oneserviceforthis
purposeishttp://compressor.io.
Anyaudioassetspackagedwithyourappshouldbeusingafileformat
thatcanleveragehighqualitycompression,suchasMP3orAAC.
2. ForiOS,there'snotmuchthatcanbedonetofurtherreducefilesize
beyondthesettingsthatareenabledbydefaultonthereleasescheme.These
includeenablingBitcodeforappthinningandsettingthecompiler
optimizationtoFastest,Smallest[-Os].
3. ForAndroid,therearetwothingsyoucandothatcouldimprovefilesize:
InAndroidStudio,openandroid/app/build.gradleandlocatethe
followinglines,thenupdatetheirvaluestothefollowing:
defenableSeparateBuildPerCPUArchitecture=true
defenableProguardInReleaseBuilds=true
4. IfyouplantoonlytargetARM-basedAndroiddevices,wecanpreventit
frombuildingforx86altogether.Inthebuild.gradlefile,locatethesplitsabi
objectandaddthefollowinglinetonotincludex86support:
include"armeabi-v7a"
YoucanreadmoreaboutABImanagementintheAndroiddocsat:
https://developer.android.com/ndk/guides/abis
Howitworks...
Inthisrecipe,wecoveredtechniquesthatcanbeusedtoreduceappfilesize.
ThesmallertheJavaScriptbundleis,thefastertheJavaScriptinterpreterwillbe
abletoparsethecode,translatingintofasterapploadtimes,andquickerOTA
updates.Thesmallerwecankeepour.ipaand.apkfiles,thefasterouruserswill
beabletodownloadtheapp.
OptimizingthePerformanceofYour
App
Inthischapter,wewillcoverthefollowingrecipes:
OptimizingourJavaScriptcode
OptimizingtheperformanceofcustomUIcomponents
Keepinganimationsrunningat60FPS
GettingthemostoutofListView
Boostingtheperformanceofourapp
OptimizingtheperformanceofnativeiOSmodules
OptimizingtheperformanceofnativeAndroidmodules
OptimizingtheperformanceofnativeiOSUIcomponents
OptimizingtheperformanceofnativeAndroidUIcomponents
Introduction
Performanceisakeyrequirementofalmosteverysinglepieceoftechnologyin
softwaredevelopment.ReactNativewasintroducedtosolvetheissueofpoor
performancethatexistedinhybridappsthatwrapwebapplicationsinanative
container.ReactNativehasanarchitecturethatlendsitselftobothflexibilityand
excellentperformance.
WhenconsideringtheperformanceofaReactNativeapp,itisimportanttothink
aboutthebigpictureofhowReactNativeworks.Therearethreemajorpartstoa
ReactNativeapp,andtheirrelativeperformanceisdepictedinthefollowing
diagram:
Therecipesinthischapterfocusonusinglower-levelfunctionsthattakeupless
memoryandhavefeweroperations,thusloweringthetimeittakesforataskto
complete.
OptimizingourJavaScriptcode
It'ssafetosaythatyourReactNativeappswillprobablybewrittenmostlyin
JavaScript.TheremaybesomenativemodulesandcustomUIcomponents,but
forthemostpart,alloftheviewsandbusinesslogicwilllikelybewritteninJSX
andJavaScript.Andifyou'reusingmodernJavaScriptdevelopmenttechniques,
you'llalsobeusinglanguageconstructsintroducedwithES6,ES7,andbeyond.
ThesemaybeavailablenativelyaspartoftheJavaScriptinterpreterbundled
withReactNative(JavaScriptCore)orpolyfilledbytheBabeltranspiler.Since
JavaScriptprobablyconstitutesthemajorityofanygivenReactNativeapp,this
shouldbethefirstpartweoptimizeinordertosqueezeextraperformanceoutof
theapp.
ThisrecipewillprovidesomehelpfultipsforoptimizingJavaScriptcodeto
makeitasperformantaspossible.
Gettingready
ThisrecipeisnotnecessarilydependentonReactNative,sinceitfocusesonthe
JavaScriptthat'susedtowriteanyReactapp.Someofthesesuggestionsare
micro-optimizationsthatwillprobablyonlyimproveperformanceon
older/slowerdevices.Dependingonwhichdevicesyouintendtosupport,some
tipswillgofurtherthanothers.
Howtodoit...
1. Thefirstoptimizationtolookatisspeedingupiterations.Often,you'll
likelybeusingfunctionsthattakeiteratorfunctionsasarguments(forEach,
filter,andmap).Asaruleofthumb,thesewillbeslowerthandoinga
standardforloop.Ifthesizeofthecollectionyou'reiteratingoverisvery
large,thiscouldmakeadifference.Here'sanexampleofafasterfilter
function:
letmyArray=[1,2,3,4,5,6,7];
letnewArray;
//Slower:
functionfilterFn(element){
returnelement>2;
}
newArray=myArray.filter(filterFn);
//Faster:
functionfilterArray(array){
varlength=array.length,
myNewArray=[],
element,
i;
for(i=0;i<length;i++){
element=array[i];
if(element>2){
myNewArray.push(array[i]);
}
}
returnmyNewArray;
}
newArray=filterArray(myArray);
2. Whenoptimizingiterations,itcanalsobemoreperformanttoensure
thatyoustorethevariablesyouareaccessingontheiteration,somewhere
closeby:
functionfindInArray(propertyerties,appConfig){
for(leti=0;i<propertyerties.length;i++){
if(propertyerties[i].somepropertyerty===
appConfig.userConfig.permissions[0]){
//dosomething
}
}
}
functionfasterFindInArray(propertyerties,appConfig){
letmatchPermission=appConfig.userConfig.permissions[0];
letlength=propertyerties.length;
leti=0;
for(;i<length;i++){
if(propertyerties[i].somepropertyerty===matchPermission){
//dosomething
}
}
}
3. Youcanalsooptimizeyourlogicalexpressions.Keepyourfastestand
closestexecutingstatementsontheleft:
functioncanViewApp(user,isSuperUser){
if(getUserPermissions(user).canView||isSuperUser){
returntrue;
}
}
functioncanViewApp(user,isSuperUser){
if(isSuperUser||getUserPermissions(user).canView){
returntrue;
}
}
4. WhilemodernJavaScript(ES6,ES7,andsoon)constructscanbemore
enjoyabletodevelopwith,someoftheirfeaturesexecutemoreslowlythan
theirES5counterparts.Thesefeaturescanincludeforof,generators,
Object.assign,andothers.Agoodreferenceforperformancecomparisonscan
befoundathttps://kpdecker.github.io/six-speed/.
5. Itcanbehelpfultoavoidtry-catchstatements,sincetheycanaffectthe
optimizationoftheinterpreter(asisthecaseinV8).
6. Arraysshouldhavemembersthatareallofthesametype.Ifyouneedto
haveacollectionwherethetypecanvary,useanobject.
Howitworks...
JavaScriptperformanceisatopicofconstantdebate.Itissometimesdifficultto
keepupwiththelatestinperformancemetrics,sinceGoogle,Apple,Mozilla,
andtheglobalopensourcecommunityisalwayshardatworkimprovingtheir
JavaScriptengines.ForReactNative,wefocusonWebKitJavaScriptCore.
Optimizingtheperformanceof
customUIcomponents
WhilebuildingyourReactNativeapp,it'sasafebetthatyouwillbecreating
customUIcomponents.Thesecomponentscaneitherbecompositionsofseveral
othercomponentsoracomponentthatbuildsontopofanexistingcomponent
andaddsmorefunctionality.Withaddedfunctionality,complexityalso
increases.Thisincreasedcomplexityleadstomoreoperations,andinturn,the
potentialforslowdowns.Fortunately,therearesomewaystomakesurethatour
customUIcomponentsareperformingthebesttheycan.Thisrecipeshows
severaltechniquesforgettingthemostoutofourcomponents.
Gettingready
ThisreciperequiresthatyouhaveaReactNativeappwithsomecustom
components.Astheseperformancesuggestionsmayormaynotprovidevalueto
yourapp,usediscretionwhenyouchoosetoapplythesetoyourcode.
Howtodoit...
1. Thefirstoptimizationweshouldlookatiswhatistrackedinthestateobject
ofagivencomponent.Weshouldmakesurethatalltheobjectswehavein
thestatearebeingused,andthateachcanpotentiallychange,causinga
desiredre-render.Ifanypiecesofdatacanbepassedaspropertys
instead,makesuretheyarepropertys.Storinganobjectmemberasastate
insteadoftheentireobjectcanalsoincreaseperformance.
2. Takealookattherenderfunctionofeachcomponent.Theoverallgoalisto
keepthisfunctionperformingasfastaspossible,sotrytoensurethatno
long-runningprocessesoccurwithinit.Ifyoucan,cachecomputationsand
constantvaluesoutsidetherenderfunctionsothattheyarenotinstantiated
everytime.
3. IfyouhaveconditionalJSXthatmayreturnintherenderfunction,returnas
earlyaspossible.Here'satrivialexample:
//unoptimized
render(){
letoutput;
constisAdminView=this.propertys.isAdminView;
if(isAdminView){
output=(<AdminButton/>);
}else{
output=(
<Viewstyle={styles.button}>
<Text>{this.propertys.buttonLabel}</Text>
</View>
);
}
returnoutput;
}
//optimized
render(){
constisAdminView=this.propertys.isAdminView;
if(isAdminView){
return(<AdminButton/>);
}
return(
<Viewstyle={styles.button}>
<Text>{this.propertys.buttonLabel}</Text>
</View>
);
}
4. Themostimportantoptimizationwecanmakeistoskiptherendermethod
altogetherifitisn'tneeded.Thisisdonebyimplementingthe
shouldComponentUpdatemethodandreturningfalsefromit,makingitapure
component.Here'showwecanmakeacomponentaPureComponent:
importReact,{PureComponent}from'react';
exportdefaultclassButtonextendsPureComponent{
}
Howitworks...
ThemajorityofyourReactNativeappswillconsistofcustomcomponents.
Therewillbeamixofstatefulandstatelesscomponents.Ashighlightedinstep
2,theoverallgoalistorenderourcomponentintheshortestamountoftime.
Anothergaincanbeachievedifacomponentcanbearchitectedtoonlyhaveto
renderthecomponentonceandthenbeleftuntouched,ascoveredinstep4.For
moreinformationonhowpurecomponentsareusedandhowtheycanbe
beneficial,checkouthttps://60devs.com/pure-component-in-react.html.
Seealso
YoucanfindsomemoreinformationaboutReactcomponentperformance
optimizationsintheofficialdocumentationathttps://reactjs.org/docs/optimizing-per
formance.html.
Keepinganimationsrunningat60
FPS
Animportantaspectofanyqualitymobileappisthefluidityoftheuser
interface.Animationsareusedtoprovidearichuserexperience,andanyjankor
jittercannegativelyaffectthis.Animationswilllikelybeusedforallkindsof
interactions,fromchangingbetweenviews,toreactingtoauser'stouch
interactiononacomponent.Thesecondmostimportantfactorforhigh-quality
animationsistomakesurethattheydonotblocktheJavaScriptthread.Tokeep
animationsfluidandnotinterruptUIinteractions,therenderloophastorender
eachframein16.67ms,sothat60FPScanbeachieved.
Inthisrecipe,wewilltakealookatseveraltechniquesforimprovingthe
performanceofanimations.Thesetechniquesfocusinparticularonpreventing
JavaScriptexecutionfrominterruptingthemainthread.
Gettingready
Forthisrecipe,we'llassumethatyouhaveaReactNativeappthathassome
animationsdefined.
Howtodoit...
1. Firstandforemost,whendebugginganimationperformanceinReact
Native,we'llwanttoenabletheperformancemonitor.Todoso,showthe
DevMenu(shakethedeviceorcmd+Dfromthesimulator)andtapShow
PerfMonitor.
TheoutputiniOSwilllooksomethinglikethefollowingscreenshot:
TheoutputinAndroidwilllooksomethinglikethefollowing
screenshot:
2. Ifyouarelookingtoanimateacomponent'stransition(opacity)or
dimensions(width,height),thenmakesuretouseLayoutAnimation.Youcanfind
anexampleofusingLayoutAnimationinChapter6,AddingBasicAnimationsto
YourApp,intheExpandingandcollapsingcontainersrecipe.
IfyouwanttouseLayoutAnimationonAndroid,youneedtoaddthefollowingcodewhenyour
applicationstarts:UIManager.setLayoutAnimationEnabledExperimental
&&UIManager.setLayoutAnimationEnabledExperimental(true).
3. Ifyouneedfinitecontrolovertheanimations,itisrecommendedthatyou
usetheAnimatedlibrarythatcomeswithReactNative.Thislibraryallows
youtooffloadalloftheanimationworkontothenativeUIthread.Todoso,
wehavetoaddtheuseNativeDriverpropertytoourAnimatedcall.Let'stakea
sampleAnimatedexampleandoffloadittothenativethread:
componentWillMount(){
this.setState({
fadeAnimimation:newAnimated.Value(0)
});
}
componentDidMount(){
Animated.timing(this.state.fadeAnimimation,{
toValue:1,
useNativeDriver:true
}).start();
}
Currently,onlyasubsetofthefunctionalityoftheAnimatedlibrarysupportsnative
offloading.PleaserefertotheThere'smore...sectionforacompatibilityguide.
4. Ifyouareunabletooffloadyouranimationworkontothenativethread,
thereisstillasolutionforprovidingasmoothexperience.Wecanusethe
InteractionManagertoexecuteataskaftertheanimationshavecompleted:
componentWillMount(){
this.setState({
isAnimationDone:false
});
}
componentWillUpdate(){
LayoutAnimation.easeInAndOut();
}
componentDidMount(){
InteractionManager.runAfterInteractions(()=>{
this.setState({
isAnimationDone:true
});
})
}
render(){
if(!this.state.isAnimationDone){
returnthis.renderPlaceholder();
}
returnthis.renderMainScene();
}
5. Finally,ifyouarestillsufferingfrompoorperformance,you'llhaveto
eitherrethinkyouranimationstrategyorimplementthepoorlyperforming
viewasacustomUIviewcomponentonthetargetplatform(s).Youwill
havetoimplementbothyourviewandanimationnativelyusingtheiOS
and/orAndroidSDK.InChapter11,AddingNativeFunctionality,we
coveredcreatingcustomUIcomponentsintheRenderingcustomiOSview
componentsandRenderingcustomAndroidviewcomponentsrecipes.
Howitworks
ThetipsinthisrecipefocusonthesimplegoalofpreventingtheJavaScript
threadfromlocking.ThemomentourJavaScriptthreadbeginstodropframes
(lock),welosetheabilitytointeractwithourapplication,evenifit'sfora
fractionofasecond.Itmayseeminconsequential,buttheeffectisfelt
immediatelybyasavvyuser.Thefocusofthetipsinthisrecipeistooffload
animationsontotheGPU.Whentheanimationisrunningonthemainthread
(thenativelayer,renderedbytheGPU),theusercaninteractwiththeappfreely
withoutstuttering,hanging,jank,orjitters.
There'smore...
Here'saquickreferenceforwhereuseNativeDriverisusable:
Function iOS Android
style,value,propertys
decay
timing
spring
add
multiply
modulo
diffClamp
interpoloate
event
division
transform
GettingthemostoutofListView
ReactNativeprovidesaprettyperformantlistcomponentoutofthebox.Itis
extremelyflexible,supportsrenderingalmostanycomponentyoucanimagine
insideofit,andrendersthemratherquickly.Ifyou'dliketoreadsomemore
examplesofhowtoworkwithListView,thereareacoupleofrecipesinthisbook,
includingrecipesinChapter2,CreatingaSimpleReactNativeApp,thatuseit.
TheReactNativeListViewisbuiltontopofScrollViewtoachievetheflexibilityof
renderingvariable-heightrowswithanyviewcomponent.
ThemajorperformanceandresourcedrawbackoftheListViewcomponentoccurs
whenyouareworkingwithanextremelylargelist.Astheuserscrollsthrough
thelist,thenextpageofrowsisrenderedatthebottom.Theinvisiblerowsatthe
topcanbesettoberemovedfromtherendertree,whichwewillcovershortly.
However,thereferencestotherowsarestillinmemoryaslongasthe
componentismounted.Naturally,asourcomponentusesuptheavailable
memory,therewillbelessroomforquicklyaccessiblestoragefortheupcoming
components.Thisrecipewillcoverdealingwithsomeofthesepotential
performanceandmemoryresourceissues.
Gettingready
Forthisrecipe,weassumethatyouhaveaReactNativeappthatismakinguse
ofaListView,preferablywithalargedataset.
Howtodoit...
1. Let'sstartwithsomeoptimizationswecanmaketoourvanillaListView
component.IfwesettheinitialListSizepropertyto1,wecanspeedupthe
initialrendering.
2. Next,wecanbumpupthepageSizeifthecomponentbeingrenderedineach
rowisnotcomplex.
3. AnotheroptimizationissettingthescrollRenderAheadDistancetoacomfortable
value.Ifyoucanexpectuserstorarelyscrollpasttheinitialviewport,or
thatthey'relikelytoscrollslowly,thenyoucanlowerthevalue.This
preventstheListViewfromrenderingtoomanyrowsinadvance.
4. Finally,thelastoptimizationwecanmakeuseofistheremoveClippedSubviews
property.However,theofficialdocumentationstatesthefollowing:
"Thefeaturemayhavebugs(missingcontent)insomecircumstances-useatyourownrisk."
5. Combiningsteps1tostep4canbeseeninthefollowingexamplecode:
renderRow(row){
return(
<Viewstyle={{height:44,overflow:'hidden'}}>
<Text>Item{row.index}</Text>
</View>
)
}
render(){
return(
<Viewstyle={{flex:1}}>
<ListView
dataSource={this.state.dataSource}
renderRow={this.renderRow}
pageSize={10}
initialListSize={1}
pageSize={10}
scrollAheadDistance={200}
/>
</View>
)
}
Howitworks...
Aswithdevelopinganyapp,themoreflexibleandcomplexsomethingis,the
sloweritperforms.ListViewisanexcellentexampleofthisconcept.Itis
extremelyflexible,sinceitcanrenderanyViewinarow,butitcanquicklybring
yourapplicationtoahaltifnotusedcarefully.Theresultoftheoptimizations
definedinstep1tostep4willvaryacrossdifferentsituationsbasedonwhatyou
arerenderingandthedatastructurethatisbeingusedbytheListView.Youshould
experimentwiththesevaluesuntilyoufindagoodbalance.Asalastresort,if
youarestillunabletoachievetherequiredperformancebenchmark,youcan
lookatsomeofthecommunitymodulesthatprovidenewListView
implementationsoralternatives.
Seealso
Thefollowingisalistofsomeofthethird-partyListViewimplementationsthat
promiseincreasedperformance:
recyclerlistview:ThislibraryisthemostrobustalternativetoListView,
boastingalonglistofimprovementsandfeatures,includingsupport
forstaggeredgridlayouts,horizontalmode,andfootersupport.The
repositoryislocatedathttps://github.com/Flipkart/recyclerlistview.
react-native-sglistview:ThistakesremoveClippedSubviewstothenextlevelby
flushingthememorywhentheoffscreenrowsareremovedfromtherender
tree.Therepositoryislocatedathttps://github.com/sghiassy/react-native-sglistv
iew.
Boostingtheperformanceofourapp
ThereasonforReactNative'sexistenceisbuildingnativeappswithJavaScript.
ThisisdifferentthansimilarframeworkssuchasIonicorCordovahybridapp,
whichwrapawebapplicationwritteninJavaScriptandattempttoemulate
nativeappbehavior.ThosewebapplicationsonlyhaveaccesstonativeAPIsfor
performingprocessing,butcannotrendernativeviewsinsidetheirapps.Thisis
onemajorbenefittoReactNativeapps,thusmakingtheminherentlyfasterthan
hybridapps.Sinceit'ssomuchmoreperformantoutofthebox,wegenerallydo
nothavetoworryaboutoverallperformanceasmuchaswewouldwithahybrid
webapp.Still,withalittleextraeffort,aslightimprovementinperformance
mightbeachievable.Thisrecipewillprovidesomequickwinsthatwecanuse
tobuildfasterReactNativeapps.
Howtodoit...
1. Thesimplestoptimizationwecanmakeistonotoutputanystatementsto
theconsole.Performingaconsole.logstatementisnotastrivialataskas
you'dimaginefortheframework,soit'srecommendedtoremoveall
consolestatementswhenyouarereadytobundleyourfinalapp.
2. Ifyouusealotofconsolestatementsduringdevelopment,youcanhave
Babelautomaticallyremovethemwhencreatingthebundlebyusing
thetransform-remove-consoleplugin.Thiscanbeinstalledintotheprojectvia
theTerminalusingyarn:
yarnaddbabel-plugin-transform-remove-console
3. Alternatively,youcanusenpm:
npminstallbabel-plugin-transform-remove-console--save
Withthepackageinstalled,youcanaddittotheprojectbyadding
a.babelrcfilecontainingthefollowingcode:
{
"presets":["react-native"],
"env":{
"production":{
"plugins":["transform-remove-console"]
}
}
}
4. Next,makesurethatwhenyou'reanalyzingyourperformance,yourappis
runninginproductionmode,preferablyonadevice.Ifyouarecurious
abouthowtodothis,youcanrefertotheDeployingtestbuildsto
HockeyApprecipeinChapter13,DeployingOurApp.
5. Sometimes,whenyouareanimatingthepositionorlayoutofaView,you
maynoticeperformancedipsintheUIthread.Youcanmitigatethisby
settingtheshouldRasterizeIOSandrenderToHardwareTextureAndroidpropertiesto
trueforiOSandAndroidplatforms.Bemindfulthatthismayincrease
memoryusagesignificantly,sobesuretotesttheperformanceafterthese
changesaswell.
6. Ifyoufindthatyouneedtotransitionviewsusinganavigationstatechange
whilealsoperformingsynchronous,potentiallylong-runningprocesses,it
canbecomeaperformancebottleneck.Thiscommonlyoccurswhen
buildingaDataSourceforaListVieworwhentransformingdatatopowerthe
upcomingview.Youshouldexperimentwithprocessingonlyaninitial
subsetofthedata,enoughtorendertheUIquicklyenough.Oncethe
animationcompletesbetweenpagetransitions,youcanuseInteractionManager
toloadtherestofthedata.YoucanrefertotheKeepinganimationsrunning
at60FPSrecipeformoreinformationonhowtouseInteractionManager.
7. Finally,ifyouhaveidentifiedaparticularcomponentortaskthatisslowing
downyourapp,andcannotfindaviablesolution,thenyoushouldconsider
movingittothenativethreadbycreatinganativemoduleornativeUI
componenttoimplementthispieceoffunctionality.
Howitworks...
Thisrecipecoverssomehigher-levelandbroader-scopedtipsforallReact
Nativeapps.Themostsignificantperformancegainsyouwilllikelyseefrom
thesetipsarefrommovingacomponenttothenativelayer,ascoveredinstep7.
Optimizingtheperformanceofnative
iOSmodules
Often,whenbuildingaReactNativeapp,youwillneedtoworkwithnative
AndroidandiOScode.Youmayhavebuiltthesenativemodulestoexposesome
extrafunctionalityprovidedbyanativeAPI,orperhapsyourappneededto
performanintensivebackgroundtask.
Aswastouchedonearlier,workinginthenativelayerreallyallowsyoutomake
useofadevice'sfullcapacity.However,itdoesn'tmeanthatthecodewewrite
willautomaticallybethefastestitcouldbe.There'salwaysroomtooptimizeand
achieveperformancegains.
Inthisrecipe,wewillprovidesometipsonhowtomakeyourObjective-Ccode
runabitfasterusingtheiOSSDKs.WewillalsoconsiderhowReactNativeand
theReactNativebridge,whichisusedtocommunicatebetweentheJavaScript
andthenativelayers,fitintothebiggerpicture.
Gettingready
Forthisrecipe,youshouldhaveaReactNativeappthatusesnativemodulesthat
havebeencreatedforiOS.Ifyouneedhelpwithwritingnativemodules,takea
lookattheExposingcustomiOSmodulesrecipeinChapter11,AddingNative
Functionality.
Howtodoit...
1. Firstandforemost,whenworkingwithnativemodules,wehavetobe
mindfulofthedatagoingthroughtheReactNativebridge.Keepingthe
dataincross-bridgeeventsandcallbackstoaminimumisalwaysthegoal,
sincethedataserializationbetweenObjective-CandJavaScriptisvery
slow.
2. Ifyouneedtokeepdatacachedinmemoryforconsumptionbythenative
module,keepitstoredinalocalpropertyorfieldvariable.Nativemodules
aresingletons.Dothisinsteadofreturningalargeobjecttostoreinthe
ReactNativecomponent.
3. Sometimes,wehavetoleverageclassesthatarelargebecausetheyare
robustintheirfeatureset.FortheObjective-CandiOSsideofthings,
insteadofinstantiatingsomethinglikeNSDateFormatterinyourmethodeach
timethatyouexposethefeatureviaRCT_EXPORT_METHOD,storethereferenceof
thisclassasapropertyertyoraninstancevariable.
4. Furthermore,nativemethodssuchasNSDateFormatterareoftenextremely
heavy,soavoidingthemisadvisablewherepossible.Forinstance,ifyour
applicationcandealwithjustUNIXtimestamps,thenyoucaneasilygetan
NSDateobjectfromatimestampwiththefollowingfunction:
-(NSDate*)dateFromUnixTimestamp:(NSTimeInterval)timestamp{
return[NSDatedateWithTimeIntervalSince1970:timestamp];
}
5. Themostsignificantperformanceoptimizationyoucanmake,ifthe
situationpresentsitself,isspawningasynchronousbackgroundthreadsto
handleintensiveprocessing.ReactNativefitsthismodelwell,sinceituses
anasynchronousmessaging/eventsystemtocommunicatebetweenthe
JavaScriptandnativethreads.Whenyourbackgroundprocessiscomplete,
youcaneitherinvokeacallback/promiseorfireaneventfortheJavaScript
threadtopickup.Tolearnhowtocreateandleveragebackground
processesinReactNativeiOSnativemodules,checkouttheBackground
processingoniOSrecipeinChapter11,AddingNativeFunctionality.
Howitworks...
Objective-Ccodeexecutesveryquickly–almostasquicklyasvanillaC.
Therefore,theoptimizationsweperformdonothavemuchtodowithexecuting
tasksbutratherwithhowthingsareinstantiatedandbynotblockingnative
threads.Thebiggestperformanceboostyou'llseeisbypropertyusingthe
GrandCentralDispatch(GCD)tospawnbackgroundprocesses,asdescribed
instep5.
Optimizingtheperformanceofnative
Androidmodules
WhiledevelopingyourReactNativeapplication,youmayfindyourselfwriting
nativeAndroidmodulestoeithercreatecross-platformfeaturesonbothiOSand
AndroidortomakeuseofnativeAPIsthathavenotbeenwrappedasfirst-party
modulesforAndroidbutthatdoexistoniOS.Hopefully,youfoundsomeuseful
adviceonworkingwithnativemodulesinChapter11,AddingNative
Functionality.
Inthisrecipe,wewillcoverseveraltechniquesforspeedingupourReactNative
Androidnativemodules.Manyofthesetechniquesarelimitedtogeneral
developmentonAndroid,andafewwilladdresscommunicatingwiththeReact
NativeJavaScriptlayer.
Gettingready
Forthisrecipe,youshouldhaveaReactNativeappthatmakesuseofthenative
modulesyoucreatedforAndroid.Ifyouneedhelpwithwritingnativemodules,
pleasetakealookattheExposingcustomAndroidmodulesrecipeinChapter
11,AddingNativeFunctionality.
Howtodoit...
1. Firstandforemost,justaswithiOSnativemodules,you'llwanttolimitthe
amountofdatacrossingtheReactNativebridge.Keepingthedatathat'sin
eventsandcallbackstoaminimumwillhelptoavoidslowdownscausedby
theserializationbetweenJavaandJavaScript.Also,aswithiOS,trytokeep
datacachedinmemorytobeusedbythenativemodule;keepitstoredina
privatefield.Nativemodulesaresingletons.Thisshouldbeleveraged
insteadofreturningalargeobjecttostoreintheReactNativecomponent.
2. WhenwritingJavacodeforAndroid,youshoulddoyourbesttoavoid
creatingshort-termobjects.Ifyoucan,useprimitives,especiallyfor
datasetssuchasarrays.
3. Itisbettertoreuseobjectsinsteadofrelyingonthegarbagecollectorto
pickupanunusedreferenceandinstantiateanewobject.
4. TheAndroidSDKprovidesamemory-efficientdatastructureforreplacing
theuseofaMap,whichmapsintegerstoobjects,calledSparseArray.Usingit
canreducememoryusageandimproveperformance.Here'sanexample:
SparseArray<SomeType>map=newSparseArray<SomeType>();
map.put(1,myObjectInstance);
ThereisalsoSparseIntArray,whichmapsintegerstointegers,andSparseBooleanArray,whichmaps
integerstoBooleanvalues.
5. WhileitmaysoundcounterintuitivetodevelopersusedtoOOP
developmentinJava,avoidingtheuseofgettersandsettersbyaccessing
theinstancefielddirectlycanalsoimproveperformance.
6. Ifyou'reeverworkingwithStringconcatenation,makeuseofStringBuilder.
7. Lastly,themostsignificantperformanceoptimizationyoucanmake,if
possible,isspawningasynchronousbackgroundthreadstoperformheavy
computationsbyleveragingReactNative'sasynchronousmessaging/event
systemtocommunicatebetweentheJavaScriptandnativethreads.When
yourbackgroundprocessiscomplete,youcaneitherinvokea
callback/PromiseorfireaneventfortheJavaScriptthreadtopickup.To
learnhowtocreatebackgroundprocessesinReactNativeAndroidnative
modules,pleasereadtheBackgroundprocessingonAndroidrecipeinChapte
r11,AddingNativeFunctionality.
Howitworks...
Themajorityofthetipsinthisreciperevolvearoundefficientmemory
management.TheAndroidOSusesatraditional-stylegarbagecollectorsimilar
tothedesktopJavaVM.Whenthegarbagecollectorkicksin,itcantake
anywherebetween100-200mstofreememory.Steps3-6allprovidesuggestions
thatreducetheapp'smemoryusage.
Optimizingtheperformanceofnative
iOSUIcomponents
ReactNativeprovidesuswithanexcellentfoundationtobuildalmostanykind
ofuserinterfaceusingbuilt-incomponentsandstyling.Componentsbuiltin
Objective-CusingtheiOSSDK,OpenGL,orsomeotherdrawinglibrarywill
generallyperformbetterthancomposingtheprebuiltcomponentsusingJSX.
Whenusingthesenativeviewcomponents,therearesomeusecasesthatmay
haveanegativeimpactonappperformance.
ThisrecipewillfocusongettingthemostoutoftheiOSUIKitSDKwhen
renderingcustomviews.Ourgoalistorendereverythingasquicklyaspossible
forourapplicationtorunat60FPS.
Gettingready
Forthisrecipe,youshouldhaveaReactNativeappthatrenderscustomnative
UIcomponentsyouhavewrittenforiOS.IfyouneedhelpwithwrappingUI
componentsinReactNative,pleasetakealookattheExposingcustomiOSview
componentsrecipeinChapter11,AddingNativeFunctionality.
Howtodoit...
1. Asmentionedpreviously,onlypassdataacrosstheReactNativebridge
whenitisunavoidabletodootherwise,sincedataserializationbetween
Objective-CandJavaScripttypesisslow.
2. Ifthereisdatathatyouneedtostoreforreferencingsometimeinthenear
future,it'sbettertostoreitinthenativeclassthatyouinitialized.
Dependingonyourapplication,youcaneitherstoreitasapropertyertyon
theViewManager,asingletonthatservesinstancesoftheView,orapropertyerty
ontheViewitself.
3. IfyourviewcomponentinvolvesrenderingmultipleUIViewinstancesas
childrenofaparentUIViewcontainer,makesurealltheinstanceshavethe
opaquepropertyertysettotrue.
4. Ifyouarerenderinganimageinsideyourviewcomponent(notusingthe
ReactNativeImagecomponent),thensettingyourimagetobethesame
dimensionastheUIImageViewcomponentcanhelpperformance.Scaling,and
otherimagetransformations,areheavyoperationsthatcanimpactonframe
rate.
5. OneofthemostimpactfultweaksinwritingiOSviewcomponentsis
avoidingoffscreenrendering.AvoiddoingthefollowingwithSDK
functionalityifpossible:
UsingclassesthatstartwiththeCoreGraphics(CG)library
OverridingthedrawRectimplementationofUIView
SettingshouldRasterize=YES,orusingsetMasksToBoundsorsetShadowonyour
UIViewinstance'slayerproperty
CustomdrawingsusingCGContext
6. Ifyouneedtoaddashadowtoyourview,makesuretosettheshadowPathto
preventoffscreenrendering.Here'sanexampleofhowtheinitializationand
shadowdefinitionshouldlook:
RCT_EXPORT_MODULE()
-(UIView*)view{
UIView*view=[[UIViewalloc]init];
view.layer.masksToBounds=NO;
view.layer.shadowColor=[UIColorblackColor].CGColor;
view.layer.shadowOffset=CGSizeMake(0.0f,5.0f);
view.layer.shadowOpacity=0.5f;
view.layer.shadowPath=[[UIBezierPathbezierPathWithRect:view.bounds]CGPath];
returnview;
}
Howitworks...
ThisrecipefocusedonsomehelpfultipsthatallowtheGPUtodoasmuchof
theworkasitcan.ThesecondpartdiscussedhowtokeeptheloadontheGPU
aslowaspossible.Enforcingtheopaquepropertyinstep3tellstheGPUnotto
worryaboutcheckingthevisibilityofothercomponentssothatitcancalculate
transparency.Steps5andstep6preventoffscreenrendering.Offscreenrendering
generatesbitmapimagesusingtheCPU(whichisaslowprocess)and,more
importantly,itkeepstheGPUfromrenderingtheviewuntiltheimageshave
beengenerated.
Optimizingtheperformanceofnative
AndroidUIcomponents
Overthelastfewyears,AndroidnativeUIperformancehasimproved
significantly.Thisisprimarilyduetoitsabilitytorendercomponentsand
layoutsusingGPUhardwareacceleration.InyourReactNativeapp,youmay
findyourselfusingcustomviewcomponents,especiallyifyouwanttousea
built-inAndroidfeaturethathasnotyetbeenwrappedasaReactNative
component.EventhoughtheAndroidplatformhasmadeaconsciouseffortto
increasetheperformanceofitsUI,thewaycomponentsarerenderedcanquickly
negateallofthesebenefits.
Inthisrecipe,we'lldiscussafewwaystogetthebestperformanceoutofour
customAndroidviewcomponents.
Gettingready
Forthisrecipe,youshouldhaveaReactNativeapplicationthatrenderscustom
nativeUIcomponentsyouhavewrittenforAndroid.Ifyouneedhelpwith
wrappingUIcomponentsinReactNative,checkouttheExposingcustom
AndroidviewcomponentsrecipeinChapter11,AddingNativeFunctionality.
Howtodoit...
1. Asstatedpreviously,onlycrosstheReactNativebridgewithdatawhen
necessary.Keepthedataineventsandcallbackstoaminimumasthedata
serializationbetweenJavaandJavaScriptisslow.
2. Ifthereisdatathatyouneedtostoreforreferencingsometimeinthenear
future,it'sbettertostoreitinthenativeclassthatyou'veinitialized.
Dependingonyourapplication,youcaneitherstoreitasapropertyonthe
SimpleViewManager,asingletonthatservesinstancesoftheView,oraproperty
ontheViewitself.
3. Whenbuildingoutviews,considerthatcomponentsoftenconsistofother
childcomponents.Thesecomponentsareheldinahierarchyoflayouts.
Over-nestinglayoutscanbecomeaveryexpensiveoperation.Ifyouare
usingmulti-levelnestedLinearLayoutinstances,trytoreplacethemwitha
singleRelativeLayout.
4. YoucananalyzetheefficiencyofyourlayoutusingtheHierarchyViewer
toolthat'sbundledinsidetheAndroidDeviceMonitor.IntheMonitor,
clickWindow|OpenPerspective...|HierarchyViewandselectOK.
5. Ifyouareperformingrepeatedanimationsonyourcustomviewnativelyin
Java(notusingtheReactNativeAnimatedAPI),thenyoucanleverage
hardwarelayerstoimproveperformance.SimplyaddawithLayermethodcall
toyouranimatecall.Forexample:
myView.animate()
.alpha(0.0f)
.withLayer()
.start();
Howitworks...
Unfortunately,therearen'tthatmanyoptimizationsyoucanperformwhenit
comestorenderingAndroidUIcomponents.Theygenerallyrevolvearoundnot
over-nestinglayouts,sincethisincreasescomplexitybyordersofmagnitude.
Whenyouhavelayoutperformanceissues,theappismostlikelysufferingfrom
overusingtheGPU,oroverdrawing.OverdrawingoccurswhentheGPUrenders
anewviewoveranexistingviewthatisalreadyrendered.YoucanenableGPU
OverdrawDebuggingintheAndroidDeveloperSettingsmenu.Theorderof
severityofoverdrawingisNoColor->Blue->Green->LightRed->Dark
Red.
Instep5,weprovidedaquicktipforimprovingtheperformanceofanimations.
Thisisparticularlytrueforrepeatedanimations,sinceitcachestheanimation
outputontheGPUandreplaysit.
OtherBooksYouMayEnjoy
Ifyouenjoyedthisbook,youmaybeinterestedintheseotherbooksbyPackt:
Hands-OnDesignPatternswithReactNative
MateuszGrzesiukiewicz
ISBN:9781788994460
ExplorethedesignPatternsinReactNative
LearnthebestpracticesforReactNativedevelopment
ExplorecommonReactpatternsthatarehighlyusedwithinReactNative
development
Learntodecouplecomponentsandusedependencyinjectioninyour
applications
Explorethebestwaysoffetchingdatafromthebackendsystems
Learnthestylingpatternsandhowtoimplementcustommobiledesigns
Explorethebestwaystoorganizeyourapplicationcodeinbigcodebases
ReactandReactNative-SecondEdition
AdamBoduch
ISBN:9781789346794
LearnwhathaschangedinReact16andhowyoustandtobenefit
CraftreusablecomponentsusingtheReactvirtualDOM
Learnhowtousethenewcreate-react-native-appcommandlinetool
AugmentReactcomponentswithGraphQLfordatausingRelay
HandlestateforarchitecturalpatternsusingFlux
BuildanapplicationforwebUIsusingRelay
Leaveareview-letotherreaders
knowwhatyouthink
Pleaseshareyourthoughtsonthisbookwithothersbyleavingareviewonthe
sitethatyouboughtitfrom.IfyoupurchasedthebookfromAmazon,please
leaveusanhonestreviewonthisbook'sAmazonpage.Thisisvitalsothatother
potentialreaderscanseeanduseyourunbiasedopiniontomakepurchasing
decisions,wecanunderstandwhatourcustomersthinkaboutourproducts,and
ourauthorscanseeyourfeedbackonthetitlethattheyhaveworkedwithPackt
tocreate.Itwillonlytakeafewminutesofyourtime,butisvaluabletoother
potentialcustomers,ourauthors,andPackt.Thankyou!